docs: plan 문서 간결화 - 100줄 이하, 구현 섹션 제거
This commit is contained in:
parent
192851220a
commit
31d74211c5
@ -1,465 +1,73 @@
|
||||
# 로빙 감정 시스템 현실 적용 5단계 로드맵
|
||||
# 로빙 감정 시스템 5단계 로드맵
|
||||
|
||||
**작성자**: happybell80 & Claude
|
||||
**목적**: 감정 시스템 설계서를 현실적으로 구현 가능한 단계별 계획으로 구체화
|
||||
|
||||
## 핵심 원칙
|
||||
- **MVP 우선**: 복잡한 기능보다 작동하는 기본 기능
|
||||
- **측정 가능한 성과**: 각 단계마다 명확한 KPI
|
||||
- **점진적 복잡도**: 단순 → 복잡으로 진화
|
||||
- **서비스 분리**: 단일 장애점 방지
|
||||
- **함수형 프로그래밍 100%**: 하드코딩 0% 목표. 근거없는 하드코딩 값 절대 사용 금지
|
||||
- 모든 값은 설정 파일이나 환경변수에서 로드
|
||||
- 순수 함수 체인으로 구성
|
||||
- 상태 변경 최소화, 불변성 유지
|
||||
**작성일**: 2025-08-08
|
||||
**목적**: 7개 감정 모델 → 32개 감정 + 온톨로지 점진 확장
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: 7개 감정 기본 구현 (모델 준비 완료, 통합 대기)
|
||||
## Phase 1: 7개 기본 감정 (완료)
|
||||
|
||||
**구현**: `troubleshooting/250815_emotion_model_training.md` 참조
|
||||
- 모델: klue/bert-base → ONNX (442MB, F1 56.3%)
|
||||
- 감정: fear, surprise, anger, sadness, neutral, happiness, disgust
|
||||
- 서비스: skill-embedding 통합 완료
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: 엔트로피 기반 복잡도 (완료)
|
||||
|
||||
**구현**: `troubleshooting/251016_emotion_entropy_integration.md` 참조
|
||||
- 엔트로피로 응답 복잡도 결정
|
||||
- 설정: confidence=0.35, entropy=2.0
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: 감정 온톨로지 (부분 완료)
|
||||
|
||||
**구현 완료**: `troubleshooting/251016_emotion_ontology_basic.md` 참조
|
||||
- emotion_likelihood_ontology.py (11개 규칙)
|
||||
- OntologyReasoner.reason_with_emotion()
|
||||
|
||||
**미구현**:
|
||||
- 감정 기반 응답 톤 조정 (현재 LLM에만 의존)
|
||||
- 감정 기록 및 패턴 분석 DB
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: 32개 세밀 감정 (미구현)
|
||||
|
||||
### 목표
|
||||
"이미 학습된 7개 한국어 감정 모델을 skill-embedding에 통합"
|
||||
- Plutchik 감정 휠 기반 32개 감정
|
||||
- Primary(8) → Secondary(8) → Tertiary(16)
|
||||
|
||||
### 진행 현황 (2025-08-15 기준)
|
||||
- ✅ 모델 학습 완료 (F1 56.3%)
|
||||
- ✅ ONNX 변환 완료 (442MB)
|
||||
- ⏳ skill-embedding 서비스 통합 대기
|
||||
- ⏳ /emotion 엔드포인트 구현 필요
|
||||
### 필요 작업
|
||||
1. AI Hub 데이터 재라벨링 (7 → 32개)
|
||||
2. 모델 재학습 (다중 레이블 분류)
|
||||
3. skill-embedding API 확장
|
||||
|
||||
### 구현 범위
|
||||
```python
|
||||
# AI Hub 데이터로 학습 완료된 7개 감정
|
||||
EMOTIONS = [
|
||||
"fear", # 공포 (기본정서)
|
||||
"surprise", # 놀람 (기본정서)
|
||||
"anger", # 분노 (기본정서)
|
||||
"sadness", # 슬픔
|
||||
"neutral", # 중립
|
||||
"happiness", # 행복
|
||||
"disgust" # 혐오
|
||||
]
|
||||
# 모델 성능: F1 56.3%, Temperature Scaling 1.232
|
||||
|
||||
# 엔트로피 계산 (7개 감정)
|
||||
def calculate_entropy(probs: List[float]) -> float:
|
||||
"""7개 감정 확률값으로 엔트로피 계산"""
|
||||
return -sum(p * log(p) for p in probs if p > 0)
|
||||
```
|
||||
|
||||
### 기술 스택
|
||||
- **감정 모델**: klue/bert-base 기반 → ONNX 변환 완료
|
||||
- **서비스**: skill-embedding 확장 (포트 8515, /emotion 추가)
|
||||
- **저장**: 기존 ChromaDB 활용 (메타데이터에 감정 추가)
|
||||
- **의사결정**: 엔트로피 기반 복잡도 판단
|
||||
- **기존 코드**: rb10508_micro의 memory/storage.py 재사용
|
||||
- **설정값**: confidence=0.35, entropy=2.0, temperature=1.232
|
||||
|
||||
### 성능 목표
|
||||
- 응답시간: 500ms 이내
|
||||
- 정확도: 사용자 평가 3.5/5.0
|
||||
- 메모리: 200MB 이내
|
||||
|
||||
### 데이터 준비 (완료)
|
||||
- AI Hub 한국어 대화 데이터셋 38,594개 샘플
|
||||
- 7개 감정 균형 분포
|
||||
- 학습/검증/테스트 분할 완료
|
||||
- 클래스 가중치 적용
|
||||
|
||||
### 검증 방법
|
||||
```bash
|
||||
# 단위 테스트
|
||||
pytest tests/test_basic_emotions.py
|
||||
|
||||
# 부하 테스트
|
||||
locust -f tests/load_test.py --users 10
|
||||
|
||||
# 응답시간 측정
|
||||
curl -w "@curl-format.txt" http://localhost:8503/analyze
|
||||
```
|
||||
|
||||
### 산출물
|
||||
- [x] 7개 감정 모델 학습 완료 (training_emotion) ✅ 2025-08-08
|
||||
- [x] ONNX 변환 완료 (442MB) ✅ 2025-08-13
|
||||
- [x] 모델 파일 51124 서버 배치 완료 ✅ 2025-08-13
|
||||
- [ ] skill-embedding 서비스에 /emotion 엔드포인트 추가 ⏳
|
||||
- [ ] Temperature Scaling 적용 (1.232) ⏳
|
||||
- [ ] 엔트로피 계산기 구현 ⏳
|
||||
- [ ] ChromaDB 메타데이터 통합 ⏳
|
||||
- [ ] rb10508_micro 연동 ⏳
|
||||
|
||||
### 아키텍처 결정 사항 (2025-08-12)
|
||||
**문제**: 감정 분석을 어디에 구현할 것인가?
|
||||
- Option A: 별도 skill-emotion 서비스 (아키텍처 원칙)
|
||||
- Option B: skill-embedding에 통합 (자원 효율성)
|
||||
|
||||
**최종 결정**: skill-embedding에 통합
|
||||
- 이유: ONNX Runtime 공유, 메모리 200MB 절약, 레이턴시 감소
|
||||
- 트레이드오프: 서비스 역할 혼재 vs 실용성
|
||||
- 향후: 서비스명 변경 고려 (skill-embedding → skill-ai)
|
||||
|
||||
### 현재 보유 모델 (2025-08-15)
|
||||
1. **aihub-7emotions** (메인 모델)
|
||||
- 크기: 442MB (ONNX)
|
||||
- 감정: fear, surprise, anger, sadness, neutral, happiness, disgust
|
||||
- 성능: F1 56.3%, ECE 0.090
|
||||
- 위치: /home/admin/ivada_project/onnx_models/aihub-7emotions/
|
||||
|
||||
2. **korean-sentiment-kcelectra** (보조 모델)
|
||||
- 크기: 511MB (ONNX)
|
||||
- 감정: 11개 한국어 세분화 감정
|
||||
- 성능: F1 70.72%
|
||||
- 위치: /home/admin/ivada_project/onnx_models/korean-sentiment-kcelectra/
|
||||
**예상 기간**: 2-3개월
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: 서비스 통합 및 최적화
|
||||
## Phase 5: 감정 기억 시스템 (미구현)
|
||||
|
||||
### 목표
|
||||
"skill-embedding 서비스 통합, 캐싱 구현, rb10508_micro 연동"
|
||||
### 설계
|
||||
- Neo4j에 감정 노드 저장
|
||||
- 시간별 감정 변화 추적
|
||||
- 패턴 분석 (우울증 조기 감지 등)
|
||||
|
||||
### 우선 작업 (로컬 개발자)
|
||||
1. skill-embedding에 /emotion 엔드포인트 추가
|
||||
2. ONNX 모델 로딩 및 추론 코드 구현
|
||||
3. Temperature Scaling (T=1.232) 적용
|
||||
4. rb10508_micro에서 감정 API 호출 통합
|
||||
|
||||
### 최적화 전략
|
||||
```python
|
||||
# LRU 캐시 적용
|
||||
from functools import lru_cache
|
||||
|
||||
@lru_cache(maxsize=1000)
|
||||
def get_emotion_embedding(text: str) -> np.ndarray:
|
||||
"""자주 사용되는 텍스트의 임베딩 캐싱"""
|
||||
return model.encode(text)
|
||||
|
||||
# 배치 처리
|
||||
async def batch_analyze(texts: List[str]):
|
||||
"""여러 텍스트 동시 처리"""
|
||||
embeddings = model.encode(texts, batch_size=32)
|
||||
return [analyze_emotion(emb) for emb in embeddings]
|
||||
### 구조
|
||||
```
|
||||
User -feels→ Emotion -at→ Time
|
||||
-talks_about→ Topic
|
||||
```
|
||||
|
||||
### ChromaDB 튜닝
|
||||
- 인덱스 최적화: HNSW 파라미터 조정
|
||||
- 쿼리 최적화: top-k를 10으로 제한
|
||||
- 연결 풀링: 커넥션 재사용
|
||||
|
||||
### 프로파일링
|
||||
```python
|
||||
# 병목 지점 찾기
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
cProfile.run('analyze_emotion(text)', 'profile_stats')
|
||||
stats = pstats.Stats('profile_stats')
|
||||
stats.sort_stats('cumulative').print_stats(10)
|
||||
```
|
||||
|
||||
### 성능 목표
|
||||
- 응답시간: 200ms (60% 개선)
|
||||
- 동시 처리: 50 req/s
|
||||
- 캐시 적중률: 30%
|
||||
|
||||
### 산출물
|
||||
- [x] ONNX 모델 변환 완료 (442MB) ✅
|
||||
- [ ] skill-embedding /emotion 엔드포인트 구현 ⏳
|
||||
- [ ] LRU 캐시 시스템 (5분 TTL)
|
||||
- [ ] 배치 처리 API
|
||||
- [ ] ChromaDB 감정 메타데이터 인덱싱
|
||||
- [ ] 성능 모니터링 (Grafana)
|
||||
- [ ] rb10508_micro 감정 기반 응답 톤 조정
|
||||
**예상 기간**: 1개월
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: 감정 패턴 분석 및 개인화
|
||||
## 참고
|
||||
|
||||
### 목표
|
||||
"장기 감정 패턴 추적, 사용자별 감정 프로파일 구축"
|
||||
|
||||
### 감정 패턴 분석
|
||||
```python
|
||||
# 시간별 감정 추적
|
||||
class EmotionTracker:
|
||||
def __init__(self, user_id: str):
|
||||
self.user_id = user_id
|
||||
self.history = [] # 시계열 감정 데이터
|
||||
|
||||
def track(self, emotion_result: dict):
|
||||
"""감정 결과를 시계열로 저장"""
|
||||
self.history.append({
|
||||
"timestamp": datetime.now(),
|
||||
"emotions": emotion_result["emotions"],
|
||||
"dominant": emotion_result["dominant"],
|
||||
"entropy": emotion_result["entropy"]
|
||||
})
|
||||
|
||||
def get_pattern(self, period: str = "day"):
|
||||
"""일/주/월 단위 감정 패턴 분석"""
|
||||
# 시간대별 주요 감정
|
||||
# 감정 변화 추이
|
||||
# 엔트로피 패턴
|
||||
return analyze_temporal_pattern(self.history, period)
|
||||
```
|
||||
|
||||
### 개인화 전략
|
||||
- 사용자별 감정 프로파일 생성
|
||||
- 감정 응답 히스토리 학습
|
||||
- 개인별 감정 임계값 조정
|
||||
- 엔트로피 특이점 활용 (창발적 응답)
|
||||
|
||||
### 엔트로피 기반 의사결정
|
||||
```python
|
||||
class EntropyBasedDecision:
|
||||
def __init__(self):
|
||||
self.entropy_threshold = 2.5 # 특이점 임계값
|
||||
|
||||
def should_be_creative(self, entropy: float) -> bool:
|
||||
"""높은 엔트로피일 때 창의적 응답"""
|
||||
return entropy > self.entropy_threshold
|
||||
|
||||
def adjust_response(self, response: str, emotion_result: dict):
|
||||
"""감정에 따른 응답 톤 조정"""
|
||||
if emotion_result["dominant"] == "sadness":
|
||||
return make_empathetic(response)
|
||||
elif emotion_result["dominant"] == "anger":
|
||||
return make_calm(response)
|
||||
elif self.should_be_creative(emotion_result["entropy"]):
|
||||
return make_creative(response)
|
||||
return response
|
||||
```
|
||||
|
||||
### 성능 목표
|
||||
- 패턴 분석: 일 1회 배치 처리
|
||||
- 프로파일 업데이트: 실시간
|
||||
- 감정 히스토리: 30일 보관
|
||||
- 개인화 정확도: 70% 이상
|
||||
|
||||
### 산출물
|
||||
- [ ] 감정 패턴 분석기
|
||||
- [ ] 사용자 감정 프로파일 DB
|
||||
- [ ] 엔트로피 기반 의사결정 모듈
|
||||
- [ ] 시계열 감정 시각화
|
||||
- [ ] 개인화 응답 전략
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: 베이지안 학습 시스템
|
||||
|
||||
### 목표
|
||||
"실시간 학습과 개인화된 감정 모델 구축"
|
||||
|
||||
### 베이지안 파라미터
|
||||
```python
|
||||
class BayesianEmotionModel:
|
||||
def __init__(self):
|
||||
# Dirichlet 사전분포 (9개 감정)
|
||||
self.emotion_prior = np.ones(9)
|
||||
|
||||
# Beta 분포 (저장 결정)
|
||||
self.save_alpha = 1
|
||||
self.save_beta = 1
|
||||
|
||||
# Gamma 분포 (응답 길이)
|
||||
self.length_k = 2
|
||||
self.length_theta = 50
|
||||
|
||||
def update_posterior(self, observation):
|
||||
"""관측값으로 사후분포 업데이트"""
|
||||
self.emotion_prior += observation['emotion_counts']
|
||||
|
||||
if observation['saved']:
|
||||
self.save_alpha += 1
|
||||
else:
|
||||
self.save_beta += 1
|
||||
|
||||
# Gamma 업데이트 (moment matching)
|
||||
self.length_k, self.length_theta = \
|
||||
self.update_gamma(observation['response_length'])
|
||||
```
|
||||
|
||||
### 예측-평가 루프
|
||||
```python
|
||||
async def prediction_evaluation_loop(user_input):
|
||||
# 1. 예측
|
||||
prediction = model.predict_user_response(user_input)
|
||||
|
||||
# 2. 실제 응답 생성
|
||||
actual_response = await generate_response(user_input)
|
||||
|
||||
# 3. 사용자 반응 수집
|
||||
user_reaction = await collect_feedback()
|
||||
|
||||
# 4. 오차 계산 (3종)
|
||||
kl_div = calculate_kl(prediction, user_reaction)
|
||||
brier = calculate_brier(prediction, user_reaction)
|
||||
ece = calculate_ece(prediction, user_reaction)
|
||||
|
||||
# 5. 모델 업데이트
|
||||
if max(kl_div, brier, ece) > threshold:
|
||||
model.update_posterior(user_reaction)
|
||||
|
||||
return actual_response
|
||||
```
|
||||
|
||||
### 개인화
|
||||
- 사용자별 베이지안 파라미터 저장
|
||||
- 조직/팀/개인 3단계 계층 구조
|
||||
- Cold start: 조직 평균값 사용
|
||||
|
||||
### 성능 목표
|
||||
- ECE: ≤ 0.08
|
||||
- Brier Score: ≤ 0.20
|
||||
- 학습 수렴: 50회 상호작용
|
||||
- 개인화 효과: +15% 만족도
|
||||
|
||||
### 산출물
|
||||
- [ ] 베이지안 모델 클래스
|
||||
- [ ] 예측-평가 파이프라인
|
||||
- [ ] 3종 오차 메트릭
|
||||
- [ ] 사용자별 파라미터 저장소
|
||||
- [ ] 학습 곡선 분석
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: 프로덕션 및 확장
|
||||
|
||||
### 목표
|
||||
"안정적인 프로덕션 배포와 고급 기능 추가"
|
||||
|
||||
### 프라이버시 게이트
|
||||
```python
|
||||
class PrivacyGate:
|
||||
def __init__(self):
|
||||
self.pii_patterns = load_pii_patterns()
|
||||
self.sensitive_topics = load_sensitive_topics()
|
||||
|
||||
def filter(self, text, metadata):
|
||||
# PII 감지
|
||||
if self.detect_pii(text):
|
||||
return self.anonymize(text)
|
||||
|
||||
# 민감 주제 필터
|
||||
if self.is_sensitive(text):
|
||||
return {"summary": self.summarize(text),
|
||||
"original": None}
|
||||
|
||||
# 24시간 옵트아웃
|
||||
if metadata.get('opt_out_requested'):
|
||||
return None
|
||||
|
||||
return text
|
||||
```
|
||||
|
||||
### 모니터링 시스템
|
||||
```yaml
|
||||
# prometheus metrics
|
||||
metrics:
|
||||
- emotion_analysis_duration_seconds
|
||||
- emotion_cache_hit_ratio
|
||||
- bayesian_update_count
|
||||
- prediction_error_rate
|
||||
- privacy_filter_triggers
|
||||
|
||||
alerts:
|
||||
- name: HighECE
|
||||
expr: emotion_ece > 0.1
|
||||
for: 5m
|
||||
|
||||
- name: SlowResponse
|
||||
expr: emotion_p95_latency > 500
|
||||
for: 10m
|
||||
```
|
||||
|
||||
### 고급 기능
|
||||
- HDBSCAN 클러스터링 도입
|
||||
- 감정 전환 패턴 학습
|
||||
- 멀티모달 확장 준비 (음성/표정)
|
||||
- 설명가능 AI (LIME/SHAP)
|
||||
|
||||
### 확장성
|
||||
```python
|
||||
# 수평 확장 준비
|
||||
class EmotionAnalyzerCluster:
|
||||
def __init__(self, workers=4):
|
||||
self.workers = workers
|
||||
self.load_balancer = ConsistentHash()
|
||||
|
||||
async def analyze(self, text, user_id):
|
||||
# 사용자별로 일관된 워커 할당
|
||||
worker = self.load_balancer.get_worker(user_id)
|
||||
return await worker.analyze(text)
|
||||
```
|
||||
|
||||
### 최종 KPI
|
||||
- ECE: ≤ 0.05
|
||||
- Brier Score: ≤ 0.18
|
||||
- NDCG@10: ≥ 0.6
|
||||
- 응답시간 P95: ≤ 300ms
|
||||
- 가용성: 99.9%
|
||||
|
||||
### 산출물
|
||||
- [ ] 프라이버시 게이트 시스템
|
||||
- [ ] Prometheus/Grafana 대시보드
|
||||
- [ ] 수평 확장 아키텍처
|
||||
- [ ] HDBSCAN 클러스터링
|
||||
- [ ] 프로덕션 배포 (Docker/K8s)
|
||||
- [ ] 운영 문서 및 Runbook
|
||||
|
||||
---
|
||||
|
||||
## 리스크 및 완화 방안
|
||||
|
||||
### 기술적 리스크
|
||||
1. **ChromaDB 성능 한계**
|
||||
- 완화: Redis 캐시 레이어 추가
|
||||
- 대안: Pinecone/Weaviate 검토
|
||||
|
||||
2. **모델 추론 속도**
|
||||
- 완화: ONNX 변환, 양자화
|
||||
- 대안: DistilBERT 기반 경량 모델
|
||||
|
||||
3. **베이지안 계산 복잡도**
|
||||
- 완화: 근사 알고리즘 사용
|
||||
- 대안: 단순 EMA로 대체
|
||||
|
||||
### 데이터 리스크
|
||||
1. **라벨 품질**
|
||||
- 완화: 다중 라벨러, 합의 메커니즘
|
||||
- 대안: 약지도 학습
|
||||
|
||||
2. **개인정보 유출**
|
||||
- 완화: 로컬 처리, 암호화
|
||||
- 대안: 연합 학습
|
||||
|
||||
### 운영 리스크
|
||||
1. **서비스 장애**
|
||||
- 완화: Circuit breaker, 폴백
|
||||
- 대안: 기본 감정만 제공
|
||||
|
||||
2. **비용 증가**
|
||||
- 완화: 사용량 기반 스케일링
|
||||
- 대안: 엣지 디바이스 처리
|
||||
|
||||
---
|
||||
|
||||
## 성공 기준
|
||||
|
||||
### Phase별 체크포인트
|
||||
- **Phase 1**: 5개 감정 인식 작동 확인
|
||||
- **Phase 2**: 200ms 응답시간 달성
|
||||
- **Phase 3**: 9개 감정 정확도 80%
|
||||
- **Phase 4**: 개인화 효과 측정 가능
|
||||
- **Phase 5**: 프로덕션 안정성 99.9%
|
||||
|
||||
### 전체 프로젝트 성공 지표
|
||||
1. **사용자 만족도**: NPS 40 이상
|
||||
2. **기술 성능**: 모든 KPI 목표치 달성
|
||||
3. **비즈니스 가치**: 사용자 이탈률 20% 감소
|
||||
4. **확장 가능성**: 일 100만 요청 처리
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
*이 로드맵은 이상적인 설계를 현실적으로 구현 가능한 단계로 나눈 실행 계획입니다. 각 Phase는 독립적으로 가치를 제공하며, 상황에 따라 중단하거나 방향을 전환할 수 있습니다.*
|
||||
- `book/200_core_design/225_온톨로지_기반_지식_표현.md`
|
||||
- `troubleshooting/250815_emotion_model_training.md`
|
||||
- `troubleshooting/251016_emotion_ontology_basic.md`
|
||||
|
||||
@ -1,264 +1,49 @@
|
||||
# 감정 분류기 통합 계획
|
||||
# 감정 분류기 Router 통합 계획
|
||||
|
||||
**날짜**: 2025-10-16
|
||||
**작성자**: Claude
|
||||
**목표**: Phase 3 감정 온톨로지를 실제 대화에 통합
|
||||
**목표**: 감정 분석을 대화 흐름에 통합
|
||||
|
||||
---
|
||||
|
||||
## 현재 상태
|
||||
## 구현 완료
|
||||
|
||||
### 구현 완료
|
||||
- ✅ EmotionClassifier (skill-embedding 연동)
|
||||
- ✅ emotion_likelihood_ontology.py (11개 규칙)
|
||||
- ✅ OntologyReasoner.reason_with_emotion()
|
||||
- ✅ ethics_constraints_ontology.py (Router 통합)
|
||||
|
||||
### 미구현
|
||||
- ❌ Router에 감정 분석 통합
|
||||
- ❌ 감정 정보를 LLM 컨텍스트에 전달
|
||||
- ❌ 실제 대화에서 감정 기반 응답
|
||||
**상세**: `troubleshooting/251016_emotion_router_integration.md` 참조
|
||||
- EmotionClassifier → skill-embedding 연동
|
||||
- Router에 감정 분석 추가
|
||||
- LLM 프롬프트에 감정 정보 전달
|
||||
|
||||
---
|
||||
|
||||
## 통합 방식 결정
|
||||
## 미구현: 감정 기반 응답 톤 자동 조정
|
||||
|
||||
### 방식 1: 증거 기반 우도 조정 (복잡)
|
||||
### 현재 한계
|
||||
- 감정 분석 → LLM 프롬프트 전달만
|
||||
- 톤 조정은 LLM 자율 판단 (불안정)
|
||||
- 명시적 규칙 없음
|
||||
|
||||
**흐름**:
|
||||
```
|
||||
사용자 메시지 → 감정 분석 → 증거 추출 → reason_with_emotion() → 조정된 증거 → LLM
|
||||
```
|
||||
### 필요 작업
|
||||
|
||||
**문제점**:
|
||||
- "증거"를 어떻게 추출할 것인가?
|
||||
- 일반 대화에서는 명확한 증거가 없음
|
||||
- Coldmail처럼 구조화된 데이터가 아님
|
||||
|
||||
**적용 가능한 경우**:
|
||||
- 의사결정 지원 (투자, 구매 등)
|
||||
- 정보 비교 (장단점 분석)
|
||||
|
||||
### 방식 2: 감정 컨텍스트 전달 (실용적) ✅ 선택
|
||||
|
||||
**흐름**:
|
||||
```
|
||||
사용자 메시지 → 감정 분석 → context에 감정 추가 → LLM (감정 고려 응답)
|
||||
```
|
||||
|
||||
**장점**:
|
||||
- 구현 간단
|
||||
- 모든 대화에 적용 가능
|
||||
- LLM이 감정을 자연스럽게 활용
|
||||
- 추후 확장 용이
|
||||
|
||||
**구현 방법**:
|
||||
**감정-톤 매핑**:
|
||||
```python
|
||||
# 1. 감정 분석
|
||||
emotion_result = await emotion_classifier.predict_async(message)
|
||||
user_emotion = emotion_result['top_label'] # fear, joy, sadness, etc.
|
||||
emotion_confidence = emotion_result['top_p']
|
||||
|
||||
# 2. context에 추가
|
||||
context['user_emotion'] = user_emotion
|
||||
context['emotion_confidence'] = emotion_confidence
|
||||
|
||||
# 3. LLM에 전달 (프롬프트에서 활용)
|
||||
llm_response = await llm_service.process_request(llm_request)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 구현 계획
|
||||
|
||||
### 1단계: 환경변수 추가
|
||||
|
||||
**.env**:
|
||||
```bash
|
||||
# Emotion Analysis (Phase 3 Ontology)
|
||||
USE_EMOTION_ANALYSIS=false # 기본값: 비활성화
|
||||
```
|
||||
|
||||
**config.py**:
|
||||
```python
|
||||
USE_EMOTION_ANALYSIS: bool = os.getenv("USE_EMOTION_ANALYSIS", "false").lower() == "true"
|
||||
```
|
||||
|
||||
### 2단계: Router에 감정 분석 통합
|
||||
|
||||
**위치**: `router.py` → `_call_internal_llm()` 메서드
|
||||
|
||||
**추가 코드**:
|
||||
```python
|
||||
# Phase 3: 감정 분석 (옵션)
|
||||
if settings.USE_EMOTION_ANALYSIS:
|
||||
try:
|
||||
from app.core.emotion.emotion_classifier import get_classifier
|
||||
emotion_classifier = get_classifier()
|
||||
|
||||
emotion_result = await emotion_classifier.predict_async(message)
|
||||
user_emotion = emotion_result['top_label']
|
||||
emotion_confidence = emotion_result['top_p']
|
||||
|
||||
# context에 추가
|
||||
if context is None:
|
||||
context = {}
|
||||
context['user_emotion'] = user_emotion
|
||||
context['emotion_confidence'] = emotion_confidence
|
||||
|
||||
logger.info(f"Emotion detected: {user_emotion} (confidence: {emotion_confidence:.2f})")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Emotion analysis failed: {e}")
|
||||
```
|
||||
|
||||
### 3단계: LLM 프롬프트에 감정 정보 활용
|
||||
|
||||
**위치**: `llm_service.py` 또는 프롬프트 생성 부분
|
||||
|
||||
**프롬프트 예시**:
|
||||
```python
|
||||
if context.get('user_emotion'):
|
||||
emotion = context['user_emotion']
|
||||
emotion_map = {
|
||||
'fear': '불안',
|
||||
'joy': '기쁨',
|
||||
'sadness': '슬픔',
|
||||
'anger': '분노',
|
||||
'surprise': '놀람',
|
||||
'disgust': '혐오',
|
||||
'trust': '신뢰',
|
||||
'neutral': '평온'
|
||||
}
|
||||
emotion_kr = emotion_map.get(emotion, emotion)
|
||||
|
||||
prompt += f"\n\n[참고] 사용자의 현재 감정 상태: {emotion_kr}"
|
||||
prompt += f"\n이 감정을 고려하여 공감적이고 적절한 응답을 제공해주세요."
|
||||
```
|
||||
|
||||
### 4단계: 응답에 감정 정보 포함 (디버깅용)
|
||||
|
||||
**응답 형식**:
|
||||
```python
|
||||
result = {
|
||||
"success": True,
|
||||
"content": final_content,
|
||||
"model_used": llm_response.model_used,
|
||||
"emotion_detected": user_emotion, # 추가
|
||||
"emotion_confidence": emotion_confidence # 추가
|
||||
EMOTION_TONE_MAP = {
|
||||
"fear": {"style": "reassuring", "emoji": False, "length": "short"},
|
||||
"anger": {"style": "calm", "emoji": False, "length": "medium"},
|
||||
"sadness": {"style": "empathetic", "emoji": True, "length": "medium"}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
**LLM 시스템 프롬프트 동적 생성**:
|
||||
- `services/llm/gemini_handler.py` 수정
|
||||
- 감정별 톤 지시 자동 삽입
|
||||
- 예: "사용자가 불안(fear)을 느낍니다. 안심시키는 톤으로 짧게 답변하세요."
|
||||
|
||||
## 테스트 계획
|
||||
|
||||
### 테스트 케이스
|
||||
|
||||
1. **불안 (fear)**:
|
||||
- 입력: "요즘 회사가 걱정돼요..."
|
||||
- 기대: 위로와 안정감 있는 응답
|
||||
|
||||
2. **기쁨 (joy)**:
|
||||
- 입력: "오늘 승진했어요!"
|
||||
- 기대: 축하와 긍정적인 응답
|
||||
|
||||
3. **슬픔 (sadness)**:
|
||||
- 입력: "실패해서 너무 속상해요"
|
||||
- 기대: 공감과 위로의 응답
|
||||
|
||||
4. **중립 (neutral)**:
|
||||
- 입력: "날씨가 어때요?"
|
||||
- 기대: 일반적인 정보 제공
|
||||
|
||||
### 검증 방법
|
||||
|
||||
```bash
|
||||
# 로그 확인
|
||||
docker logs rb8001 --tail 100 | grep -E "Emotion detected|user_emotion"
|
||||
|
||||
# 응답 확인
|
||||
# Slack에서 대화 → 로그에서 감정 정보 확인
|
||||
```
|
||||
**A/B 테스트**:
|
||||
- ON/OFF 비교
|
||||
- 사용자 만족도 측정
|
||||
|
||||
---
|
||||
|
||||
## 향후 확장 (Phase 3.5)
|
||||
## 참고
|
||||
|
||||
### reason_with_emotion() 통합
|
||||
|
||||
**적용 시나리오**: 의사결정 지원
|
||||
|
||||
**예시**:
|
||||
```python
|
||||
# 사용자: "이 투자 어떻게 생각하세요?"
|
||||
# 감정: fear (불안)
|
||||
|
||||
# 1. LLM이 증거 생성
|
||||
evidences = [
|
||||
{"type": "위험", "content": "시장 변동성", "prior_likelihood": 0.6},
|
||||
{"type": "긍정", "content": "성장 가능성", "prior_likelihood": 0.5}
|
||||
]
|
||||
|
||||
# 2. 감정 기반 조정
|
||||
adjusted, explanation = reasoner.reason_with_emotion("fear", evidences)
|
||||
# 위험 0.6 → 0.78 (+30%)
|
||||
|
||||
# 3. 조정된 증거로 최종 응답
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 성능 영향
|
||||
|
||||
### 추가 처리 시간
|
||||
- 감정 분석 API 호출: ~50-100ms
|
||||
- skill-embedding 서비스 응답 시간
|
||||
|
||||
### 완화 방법
|
||||
- 비동기 호출 (await)
|
||||
- 타임아웃 설정 (10초)
|
||||
- 실패 시 graceful degradation
|
||||
|
||||
---
|
||||
|
||||
## 롤백 방법
|
||||
|
||||
### 환경변수 비활성화
|
||||
```bash
|
||||
USE_EMOTION_ANALYSIS=false
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 일정
|
||||
|
||||
### 즉시 (오늘)
|
||||
- [x] 통합 계획 수립
|
||||
- [ ] 환경변수 추가
|
||||
- [ ] Router 코드 수정
|
||||
- [ ] 테스트 및 검증
|
||||
|
||||
### 내일 (2025-10-17)
|
||||
- [ ] 실전 데이터 수집
|
||||
- [ ] 감정 분석 정확도 확인
|
||||
|
||||
### 향후 (Phase 3.5)
|
||||
- [ ] reason_with_emotion() 통합 (의사결정 지원)
|
||||
- [ ] 감정 기반 추천 시스템
|
||||
- [ ] 베이지안 학습 (피드백 기반)
|
||||
|
||||
---
|
||||
|
||||
## 결론
|
||||
|
||||
**선택한 방식**: 감정 컨텍스트 전달 (방식 2)
|
||||
|
||||
**이유**:
|
||||
- 간단하고 실용적
|
||||
- 모든 대화에 적용 가능
|
||||
- LLM의 자연어 이해 능력 활용
|
||||
- 점진적 확장 가능
|
||||
|
||||
**다음 단계**: Router에 감정 분석 코드 추가
|
||||
- `troubleshooting/251016_emotion_router_integration.md`
|
||||
- `311_FastAPI_구조_원칙.md`
|
||||
|
||||
@ -1,22 +1,13 @@
|
||||
# 온톨로지 기반 Coldmail 필터 및 기억 시스템 구현 계획
|
||||
# 온톨로지 기반 Coldmail 필터 구현 계획
|
||||
|
||||
**날짜**: 2025-10-16
|
||||
**작성자**: Claude
|
||||
**관련 문서**: 200_core_design/225_온톨로지_기반_지식_표현.md
|
||||
**목표**: 임베딩 한계(파인티처 메일 누락)를 온톨로지 추론으로 해결
|
||||
|
||||
---
|
||||
|
||||
## 목표
|
||||
|
||||
파인티처 메일 누락 같은 임베딩 한계(75% 정확도)를 온톨로지 추론으로 해결하고, 기억-감정-윤리 삼각형을 온톨로지로 구조화하여 로빙의 존재성 강화.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Coldmail 온톨로지 파일럿 (2주)
|
||||
|
||||
### 개념 계층 구축
|
||||
**파일**: rb8001/app/ontology/coldmail_schema.owl
|
||||
## Phase 1: Coldmail 온톨로지 (미착수)
|
||||
|
||||
### 개념 계층
|
||||
```
|
||||
메일
|
||||
└── 외부메일
|
||||
@ -27,314 +18,75 @@
|
||||
└── 사업계획서
|
||||
```
|
||||
|
||||
### 관계 정의
|
||||
- `발신자 -속한다→ 회사`
|
||||
- `회사 -투자단계→ {시드, 시리즈A, ...}`
|
||||
- `메일 -포함한다→ 첨부파일`
|
||||
|
||||
### 추론 규칙 (10개)
|
||||
**파일**: rb8001/app/services/coldmail_ontology_reasoner.py
|
||||
|
||||
#### Coldmail 판정 규칙 (6개)
|
||||
1. IF 제목 CONTAINS ["투자", "IR", "피칭", "사업계획"] AND 첨부 HAS PDF THEN coldmail (확률 0.9)
|
||||
2. IF 첨부파일명 CONTAINS ["회사소개서", "IR_Deck", "Pitch", "사업계획서"] THEN coldmail (확률 0.85)
|
||||
3. IF 제목 CONTAINS "검토요청" AND 첨부 EXISTS THEN coldmail (확률 0.8)
|
||||
4. IF 발신자 NOT IN known_contacts AND 첨부 HAS PDF THEN coldmail (확률 0.7)
|
||||
5. IF 본문 CONTAINS ["투자 유치", "펀딩", "시리즈", "밸류에이션"] THEN coldmail (확률 0.75)
|
||||
6. IF 발신자.도메인 IN ["startup", "ventures", "capital"] THEN coldmail (확률 0.6)
|
||||
**Coldmail 판정 (6개)**:
|
||||
1. 제목 CONTAINS ["투자", "IR", "피칭"] + PDF 첨부 → 0.9
|
||||
2. 첨부파일명 CONTAINS ["회사소개서", "IR_Deck"] → 0.85
|
||||
3. 제목 "검토요청" + 첨부 → 0.8
|
||||
4. 미등록 발신자 + PDF → 0.7
|
||||
5. 본문 CONTAINS ["투자 유치", "펀딩", "밸류에이션"] → 0.75
|
||||
6. 발신자 도메인 IN ["startup", "ventures", "capital"] → 0.6
|
||||
|
||||
#### Normal 판정 규칙 (4개)
|
||||
7. IF 제목 CONTAINS ["행사", "초대", "안내", "세미나"] THEN normal (확률 0.9)
|
||||
8. IF 제목 CONTAINS ["영수증", "발급", "세금계산서", "견적서"] THEN normal (확률 0.95)
|
||||
9. IF 제목 CONTAINS ["회의", "공지", "보고", "업무"] THEN normal (확률 0.85)
|
||||
10. IF 발신자 IN known_contacts AND NOT (규칙 1-6) THEN normal (확률 0.8)
|
||||
**Normal 판정 (4개)**:
|
||||
7. 제목 CONTAINS ["행사", "초대", "세미나"] → 0.9
|
||||
8. 제목 CONTAINS ["영수증", "세금계산서"] → 0.95
|
||||
9. 제목 CONTAINS ["회의", "공지", "보고"] → 0.85
|
||||
10. 등록 발신자 + 규칙 1-6 미해당 → 0.8
|
||||
|
||||
#### 최종 판단 로직
|
||||
- Coldmail 규칙 매칭: 가장 높은 확률 선택
|
||||
- Normal 규칙 매칭: 가장 높은 확률 선택
|
||||
- Coldmail 확률 > Normal 확률 → Coldmail
|
||||
- Threshold: 0.7 이상 시 확정, 0.4-0.7은 LLM fallback
|
||||
### 구현 필요
|
||||
|
||||
### 검증
|
||||
- 파인티처 메일: coldmail 확률 0.9+ (현재 0.28)
|
||||
**파일**: `rb8001/app/services/coldmail_ontology_reasoner.py`
|
||||
- 규칙 엔진 구현
|
||||
- 임베딩 분류(현재 75%) + 온톨로지 추론 → 90%+ 목표
|
||||
- Threshold: 0.7 이상 확정, 0.4-0.7은 LLM fallback
|
||||
|
||||
**검증**:
|
||||
- 파인티처 메일: coldmail 0.9+ (현재 0.28)
|
||||
- 기존 17건 재테스트: 정확도 90%+
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: 기억 시스템 온톨로지 통합 (1개월)
|
||||
## Phase 2: Neo4j 기억 시스템 (미착수)
|
||||
|
||||
### Neo4j 도입
|
||||
**위치**: 51123 서버 (192.168.219.45)
|
||||
**설치 상태**: ✅ 이미 설치됨 (시스템 직접 설치, Docker 아님)
|
||||
### 인프라
|
||||
|
||||
**설치 정보**:
|
||||
- 버전: Neo4j 2025.06.2 Community Edition
|
||||
- 서비스 상태: Active (running) since 2025-08-20
|
||||
- 가동 시간: 1개월 26일
|
||||
- HTTP 포트: 7474 (브라우저 접속)
|
||||
- Bolt 포트: 7687 (드라이버 연결)
|
||||
- 인증: 활성화 (비밀번호 변경됨)
|
||||
**Neo4j 설치 완료** (51123 서버):
|
||||
- 버전: 2025.06.2 Community
|
||||
- Bolt: neo4j://192.168.219.45:7687
|
||||
- HTTP: http://192.168.219.45:7474
|
||||
|
||||
**연결 정보**:
|
||||
- Bolt URI: `neo4j://192.168.219.45:7687`
|
||||
- HTTP API: `http://192.168.219.45:7474`
|
||||
- 데이터베이스: neo4j (기본 DB), system (시스템 DB)
|
||||
### 스키마 설계
|
||||
|
||||
**스키마** (설계안):
|
||||
```
|
||||
(사건)-[:발생시각]->(시간)
|
||||
(사건)-[:관련감정]->(감정)
|
||||
(사건)-[:참여자]->(사용자)
|
||||
(사건)-[:결과]->(결과)
|
||||
(:User)-[:SENDS]->(:Email)-[:CONTAINS]->(:Attachment)
|
||||
(:Email)-[:CLASSIFIED_AS]->(:Category {name:"coldmail"})
|
||||
(:Email)-[:HAS_EMOTION]->(:Emotion {type:"fear", confidence:0.8})
|
||||
```
|
||||
|
||||
### ChromaDB + Neo4j 하이브리드 알고리즘
|
||||
**rb8001/app/services/memory_hybrid_retrieval.py**
|
||||
|
||||
#### 1단계: ChromaDB 벡터 검색 (빠른 필터링)
|
||||
```python
|
||||
# 입력: 사용자 쿼리 (예: "작년 프레젠테이션 때 어떻게 했지?")
|
||||
# 1. 쿼리 임베딩: skill-embedding (8515) /embed 호출
|
||||
# 2. ChromaDB 유사도 검색: top_k=20 (충분한 후보)
|
||||
# 3. 출력: 20개 후보 대화 (벡터 점수 포함)
|
||||
### 쿼리 예시
|
||||
```cypher
|
||||
// 투자 제안 이메일 중 긍정적 감정 메일 찾기
|
||||
MATCH (u:User)-[:SENDS]->(e:Email)-[:CLASSIFIED_AS]->(:Category {name:"coldmail"})
|
||||
WHERE e.emotion IN ["happiness", "neutral"]
|
||||
RETURN e.subject, e.sender, e.timestamp
|
||||
```
|
||||
|
||||
#### 2단계: Neo4j 그래프 추론 (의미적 연결)
|
||||
```python
|
||||
# 입력: ChromaDB 후보 20개
|
||||
# Cypher 쿼리:
|
||||
MATCH (event:사건)-[:관련감정]->(emotion)
|
||||
MATCH (event)-[:결과]->(result)
|
||||
WHERE event.id IN [후보 20개 ID]
|
||||
RETURN event, emotion, result, event.시간
|
||||
ORDER BY event.시간 DESC
|
||||
|
||||
# 우선순위:
|
||||
# - [:결과]->(성공) 관계 있는 사건 우선 (가중치 1.5배)
|
||||
# - [:관련감정]->(긴장) 매칭 시 가중치 1.3배
|
||||
# - 시간적 근접성: 1년 전 > 2년 전 (거리 역수)
|
||||
```
|
||||
|
||||
#### 3단계: 점수 통합 및 순위 결정
|
||||
```python
|
||||
최종점수 = (벡터점수 * 0.4) + (그래프점수 * 0.6)
|
||||
# 그래프점수 = 관계가중치 * 시간가중치
|
||||
# 출력: 상위 5개 사건 반환
|
||||
```
|
||||
|
||||
### API 설계
|
||||
**rb8001/app/router/memory_ontology.py**:
|
||||
- POST /memory/event: 사건 저장 (자동 관계 추론)
|
||||
- GET /memory/recall: 쿼리 기반 회상 (3단계 하이브리드)
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: 감정-윤리 온톨로지 규칙화 (1개월)
|
||||
## Phase 3: 감정-기억-윤리 삼각형 (미착수)
|
||||
|
||||
### 감정-우도 온톨로지
|
||||
**파일**: rb8001/app/ontology/emotion_likelihood.owl
|
||||
### 통합 설계
|
||||
- 감정 분석 → 기억 검색 → 윤리 제약 → 응답 생성
|
||||
- Neo4j에 감정 이력 저장
|
||||
- 패턴 분석 (우울증 조기 감지)
|
||||
|
||||
```
|
||||
불안 -조정→ 위험관련증거 (가중치 1.3)
|
||||
흥분 -조정→ 긍정관련증거 (가중치 1.2)
|
||||
슬픔 -조정→ 위로관련증거 (가중치 1.5)
|
||||
```
|
||||
|
||||
### 윤리 제약 온톨로지
|
||||
**파일**: rb8001/app/ontology/ethics_constraints.owl
|
||||
|
||||
```
|
||||
정보수집 행동 -제약→ [개인정보보호, 투명성, 동의]
|
||||
조언 행동 -제약→ [해악금지, 자율성존중]
|
||||
```
|
||||
|
||||
### 추론 엔진 통합
|
||||
**rb8001/app/services/ontology_explainer.py**
|
||||
|
||||
#### 추론 과정 추적 (Jena Rules)
|
||||
```python
|
||||
# 규칙 실행 시 추적 로그 생성
|
||||
trace = []
|
||||
for rule in matched_rules:
|
||||
trace.append({
|
||||
"rule_id": rule.id,
|
||||
"condition": rule.condition, # "사용자.감정 = 불안"
|
||||
"action": rule.action, # "위험관련증거.우도 *= 1.3"
|
||||
"matched_value": matched_value # "감정: 불안"
|
||||
})
|
||||
```
|
||||
|
||||
#### 설명 템플릿 (자연어 생성)
|
||||
```python
|
||||
def generate_explanation(trace):
|
||||
explanation = []
|
||||
for step in trace:
|
||||
if step['rule_id'].startswith('emotion_'):
|
||||
template = "사용자의 {emotion} 감정 때문에 {evidence} 관련 증거의 중요도를 {weight}배 조정했습니다."
|
||||
explanation.append(template.format(...))
|
||||
elif step['rule_id'].startswith('ethics_'):
|
||||
template = "{action} 행동은 {constraint} 원칙에 위배되어 거부했습니다. 대안: {alternative}"
|
||||
explanation.append(template.format(...))
|
||||
return " ".join(explanation)
|
||||
```
|
||||
|
||||
#### 윤리 충돌 해결 우선순위 (OWL Ontology)
|
||||
```turtle
|
||||
# ethics_constraints.owl
|
||||
:해악금지 rdf:type :윤리원칙 ; :priority 1 .
|
||||
:투명성 rdf:type :윤리원칙 ; :priority 2 .
|
||||
:자율성존중 rdf:type :윤리원칙 ; :priority 3 .
|
||||
:개인정보보호 rdf:type :윤리원칙 ; :priority 1 .
|
||||
|
||||
# 충돌 시 priority 높은 것 우선 (1 > 2 > 3)
|
||||
```
|
||||
|
||||
#### HermiT 일관성 검사
|
||||
- 규칙 간 모순 자동 탐지 (예: "불안 → 우도 증가" vs "불안 → 우도 감소")
|
||||
- 배포 전 ontology validation 자동화
|
||||
|
||||
---
|
||||
|
||||
## 기술 스택
|
||||
|
||||
| 구분 | 도구 | 용도 |
|
||||
|------|------|------|
|
||||
| 온톨로지 편집 | Protégé | OWL 스키마 설계 |
|
||||
| 그래프 DB | Neo4j | 사건-관계 저장 |
|
||||
| 추론 엔진 | HermiT | 일관성 검사 |
|
||||
| 벡터 DB | ChromaDB | 기존 유지 (하이브리드) |
|
||||
| 표준 | RDF/OWL | 온톨로지 표현 |
|
||||
|
||||
---
|
||||
|
||||
## 마일스톤
|
||||
|
||||
| 날짜 | 단계 | 목표 |
|
||||
|------|------|------|
|
||||
| Week 1-2 | Phase 1 | Coldmail 정확도 90%+ |
|
||||
| Week 3-6 | Phase 2 | 기억 회상 의미적 연결 |
|
||||
| Week 7-10 | Phase 3 | 감정-윤리 규칙 투명화 |
|
||||
|
||||
---
|
||||
|
||||
## 체크리스트
|
||||
|
||||
Phase 1: ✅ 완료 (2025-10-16)
|
||||
- [x] Coldmail 온톨로지 스키마 설계 (규칙 기반, Python 구현)
|
||||
- [x] 추론 규칙 11개 구현 (coldmail_ontology_reasoner.py)
|
||||
- [x] 파인티처 메일 재테스트 (0.90, 목표 0.9+ 달성)
|
||||
- [x] Hybrid Filter 통합 (USE_ONTOLOGY_FILTER 환경변수, 롤백 가능)
|
||||
- [ ] Slack 피드백 → 관계 가중치 베이지안 업데이트 (Phase 1.5)
|
||||
|
||||
Phase 2:
|
||||
- [x] Neo4j 설치 확인 (51123 서버) - ✅ 이미 설치됨 (2025.06.2)
|
||||
- [ ] Neo4j Python 드라이버 연동 (neo4j 패키지)
|
||||
- [ ] ChromaDB + Neo4j 하이브리드 쿼리 구현
|
||||
- [ ] "1년 전 비슷한 상황" 회상 테스트
|
||||
|
||||
Phase 3:
|
||||
- [ ] 감정-우도 온톨로지 7가지 감정
|
||||
- [ ] 윤리 제약 온톨로지 사랑 기반 원칙
|
||||
- [ ] HermiT 일관성 검사 자동화
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 구현 완료 (2025-10-16)
|
||||
|
||||
### 구현 파일
|
||||
- **rb8001/app/services/coldmail_ontology_reasoner.py**: 11개 추론 규칙
|
||||
- **rb8001/app/services/coldmail_hybrid_filter.py**: 온톨로지 통합 (환경변수 제어)
|
||||
- **rb8001/tests/test_coldmail_ontology.py**: 7개 테스트 케이스 (100% 통과)
|
||||
- **rb8001/tests/test_hybrid_simple.py**: 5개 통합 테스트 (100% 통과)
|
||||
|
||||
### 테스트 결과
|
||||
```
|
||||
파인티처 메일: 0.28 → 0.90 (4개 규칙 매칭)
|
||||
- R1: 투자 키워드 + PDF (0.90)
|
||||
- R2: 회사소개서 첨부명 (0.85)
|
||||
- R3: 검토요청 + 첨부 (0.80)
|
||||
- R3B: 투자검토 키워드 (0.65)
|
||||
- R4: 신규 발신자 + PDF (0.70)
|
||||
```
|
||||
|
||||
### 롤백 시나리오
|
||||
|
||||
#### 시나리오 1: 환경변수 롤백 (즉시, 권장)
|
||||
**증상**: 온톨로지 오판, 성능 저하
|
||||
**방법**:
|
||||
```bash
|
||||
cd /home/admin/ivada_project/rb8001
|
||||
|
||||
# .env 파일 수정
|
||||
USE_ONTOLOGY_FILTER=false
|
||||
|
||||
# Docker 재시작 (5초 소요)
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
**결과**: 기존 임베딩 필터로 즉시 복귀, 코드 변경 없음
|
||||
|
||||
#### 시나리오 2: Git 부분 롤백 (온톨로지만 제거)
|
||||
**증상**: 환경변수 롤백으로도 해결 안 될 때
|
||||
**방법**:
|
||||
```bash
|
||||
cd /home/admin/ivada_project/rb8001
|
||||
|
||||
# 온톨로지 파일만 제거
|
||||
git rm app/services/coldmail_ontology_reasoner.py
|
||||
git rm tests/test_coldmail_ontology.py tests/test_hybrid_simple.py
|
||||
|
||||
# hybrid_filter.py를 온톨로지 통합 이전 버전으로 복구
|
||||
git checkout 48aacfa^ -- app/services/coldmail_hybrid_filter.py
|
||||
|
||||
# 커밋 및 배포
|
||||
git commit -m "Rollback: Remove ontology reasoner"
|
||||
git push origin main
|
||||
docker compose down && docker compose up -d --build
|
||||
```
|
||||
|
||||
#### 시나리오 3: 전체 롤백 (Phase 1 이전)
|
||||
**증상**: 심각한 오류, 전체 되돌리기 필요
|
||||
**방법**:
|
||||
```bash
|
||||
cd /home/admin/ivada_project/rb8001
|
||||
|
||||
# Phase 1 파일럿 직전 커밋으로 복귀
|
||||
git checkout 28ef36c # 48aacfa 이전 커밋
|
||||
git push origin main --force
|
||||
|
||||
# Docker 재빌드
|
||||
docker compose down && docker compose up -d --build
|
||||
```
|
||||
**주의**: force push는 최후 수단
|
||||
|
||||
#### 롤백 포인트 커밋
|
||||
- **28ef36c**: Phase 1 이전 (안전한 복귀 지점)
|
||||
- **48aacfa**: Phase 1 파일럿 완료 (온톨로지 단독 동작)
|
||||
- **7a122f4**: Hybrid Filter 통합
|
||||
- **88636cf**: UnboundLocalError 핫픽스 (현재)
|
||||
|
||||
#### 롤백 검증
|
||||
```bash
|
||||
# 롤백 후 확인
|
||||
docker logs rb8001 --tail 50 | grep -i "ontology\|embedding"
|
||||
# "Embedding" 메시지만 보이면 롤백 성공
|
||||
```
|
||||
|
||||
### 실전 검증 계획
|
||||
- **일시**: 2025-10-17 09:05 Coldmail Daily Briefing
|
||||
- **확인 사항**:
|
||||
- [ ] 로그에서 "Stage 1 (Ontology)" 메시지 출력
|
||||
- [ ] 파인티처 유사 케이스 발생 시 온톨로지 판정 확인
|
||||
- [ ] 오류 없이 정상 동작 확인
|
||||
- [ ] 응답 속도 측정 (온톨로지 vs 기존 임베딩)
|
||||
**예상 기간**: 3-4개월
|
||||
|
||||
---
|
||||
|
||||
## 참고
|
||||
|
||||
- 설계 원칙: 200_core_design/225_온톨로지_기반_지식_표현.md
|
||||
- 문제 배경: troubleshooting/251014_claude_coldmail_filter_tokenization_issue.md
|
||||
- 온톨로지 연구: research/ontology/
|
||||
- **구현 커밋**: rb8001 88636cf (hotfix), 7a122f4 (통합), 48aacfa (파일럿)
|
||||
- `book/200_core_design/225_온톨로지_기반_지식_표현.md`
|
||||
- `troubleshooting/250815_emotion_model_training.md`
|
||||
- `troubleshooting/251016_emotion_ontology_basic.md`
|
||||
|
||||
@ -1,210 +1,85 @@
|
||||
# rb8001 계층 분리 리팩토링 계획
|
||||
|
||||
**날짜**: 2025-11-23
|
||||
**작성자**: admin
|
||||
**관련 파일**: `rb8001/main.py`, `rb8001/app/router/router.py`
|
||||
**참고**: `DOCS/book/300_architecture/311_FastAPI_구조_원칙.md`
|
||||
**참고**: `311_FastAPI_구조_원칙.md`
|
||||
|
||||
---
|
||||
|
||||
## 목적
|
||||
|
||||
rb8001의 중복 대화 저장 문제를 해결하고, FastAPI 구조 원칙(311_FastAPI_구조_원칙.md)을 준수하는 계층 분리 구조로 리팩토링
|
||||
|
||||
### 즉시 해결 필요 문제
|
||||
- 대화가 DB에 2번 저장되는 중복 저장 버그 (1회 요청 → 2회 DB INSERT)
|
||||
|
||||
### 장기 개선 목표
|
||||
- 계층 분리 원칙 위반 15건 해결
|
||||
- 유지보수성 및 확장성 향상
|
||||
- Conversation/Message 온톨로지 기반 통합 로그(actor, source_channel, channel_id, context_node 등)로 전환 + Celery/Redis 비동기 큐 도입 검토 (ChromaDB, intent_review_queue 백그라운드 처리)
|
||||
중복 대화 저장 버그 해결 및 계층 분리 원칙 준수
|
||||
|
||||
---
|
||||
|
||||
## 현재 문제
|
||||
## Phase 1: 중복 저장 해결 (완료)
|
||||
|
||||
### 1. 중복 저장 (긴급)
|
||||
|
||||
**문제 상황:**
|
||||
- router.py:876 - `_call_internal_llm`에서 1차 저장 (router → state 직접 호출)
|
||||
- main.py:110 - `save_message_conversation`에서 2차 저장 (이미 제거됨)
|
||||
- 결과: conversation_log 테이블에 동일 대화 2번 삽입
|
||||
|
||||
**원칙 위반:**
|
||||
- 311_FastAPI_구조_원칙.md:92 - 계층 건너뛰기 (router → state 직접 호출)
|
||||
- DRY 원칙 위반 (같은 로직 2곳 실행)
|
||||
|
||||
### 2. 계층 구조 위반 (15건)
|
||||
|
||||
#### 계층 건너뛰기
|
||||
1. router.py:876 - router → state 직접 호출 (원칙:92)
|
||||
2. router/feedback_handler.py:58 - SessionLocal() 직접 사용 (원칙:21, 164)
|
||||
3. router/intent_review_endpoint.py:21 - SessionLocal() 직접 사용
|
||||
4. router/slack_handler.py:77,202,361 - SessionLocal() 직접 사용
|
||||
5. services/startup_valuation.py:475 - asyncpg.connect() 직접 사용 (원칙:164)
|
||||
6. services/intent_bayes.py:57,117,160 - psycopg2.connect() 직접 사용
|
||||
7. services/coldmail_filter.py:182,208,255 - asyncpg.connect() 직접 사용
|
||||
8. main.py:694,711 - state/database 직접 호출 (원칙:92)
|
||||
|
||||
#### main.py 원칙 위반
|
||||
9. main.py:84-176 - 엔드포인트 직접 정의 (원칙:30 - "앱 실행, 라우터 등록만")
|
||||
10. main.py:118-137 - 비즈니스 로직 포함 (원칙:21)
|
||||
11. main.py:211,620 - router 내부 메서드(_call_internal_llm) 직접 호출
|
||||
|
||||
#### 비즈니스 로직 배치 오류
|
||||
12. router.py:128-682 - route_message()는 비즈니스 로직, services/에 있어야 함 (원칙:21)
|
||||
13. app/brain/, app/llm/, app/memory/ - services/brain/, services/llm/로 이동 필요 (원칙:28-42)
|
||||
|
||||
#### 구조 누락
|
||||
14. schemas/ 폴더 없음 (원칙:49)
|
||||
15. models와 state 미분리 - state/database.py에 ORM 모델 + DB 접근 혼재 (원칙:36,47)
|
||||
|
||||
#### 파일 크기 초과
|
||||
- 24개 파일이 300줄 초과 (router.py 915줄, main.py 747줄 등)
|
||||
**문제**: router.py + main.py 이중 저장
|
||||
**해결**: `troubleshooting/250924_대화저장_오류.md` 참조
|
||||
|
||||
---
|
||||
|
||||
## 해결 방안
|
||||
## Phase 2: 계층 건너뛰기 해결 (부분 완료)
|
||||
|
||||
### Phase 1: 중복 저장 해결 (긴급, 완료)
|
||||
**구현 완료**: `troubleshooting/251123_rb8001_endpoint_service_separation.md` 참조
|
||||
- main.py 엔드포인트 → routers/로 이동
|
||||
- router → services 호출 구조 일부 정리
|
||||
|
||||
**수정 완료:**
|
||||
- router.py:873-884 - `_save_conversation` 호출 제거
|
||||
- router.py:628-638, 670-680 - services 호출 추가
|
||||
- main.py:108-115 - `save_message_conversation` 호출 제거
|
||||
**미완료 위반 사항**:
|
||||
1. router/feedback_handler.py:58 - SessionLocal() 직접 사용 (원칙:164)
|
||||
2. router/intent_review_endpoint.py:21 - SessionLocal() 직접 사용
|
||||
3. router/slack_handler.py:77,202,361 - SessionLocal() 직접 사용
|
||||
4. services/startup_valuation.py:475 - asyncpg.connect() 직접 사용
|
||||
5. services/intent_bayes.py:57,117,160 - psycopg2.connect() 직접 사용
|
||||
6. services/coldmail_filter.py:182,208,255 - asyncpg.connect() 직접 사용
|
||||
|
||||
**결과:**
|
||||
- 대화 저장 1회로 축소
|
||||
- 하지만 router에서 services 호출 (임시)
|
||||
|
||||
### Phase 2 Step 1-2: 엔드포인트 및 비즈니스 로직 분리 (완료)
|
||||
|
||||
**수정 완료:**
|
||||
- app/router/message_endpoint.py 생성 - 엔드포인트 분리
|
||||
- main.py:84-176 제거 → message_endpoint.py로 이동
|
||||
- main.py에 include_router 추가
|
||||
- app/services/message_service.py 생성 - route_message 이동
|
||||
- router.py:128-682 (route_message) → message_service.py로 이동
|
||||
- router.py는 message_service 호출만 (~10줄)
|
||||
|
||||
**결과:**
|
||||
- 계층 분리 원칙 준수 (router → services → state)
|
||||
- 중복 저장 문제 해결 (services 레이어에서 한 번만 저장)
|
||||
- router.py 크기 927줄 → 366줄로 축소
|
||||
|
||||
### Phase 2 Step 3: 폴더 구조 정리 (완료)
|
||||
|
||||
**수정 완료:**
|
||||
- app/brain/ → app/services/brain/ 이동
|
||||
- app/llm/ → app/services/llm/ 이동
|
||||
- app/memory/ → app/services/memory/ 이동
|
||||
- app/skills/ → app/services/skills/ 이동
|
||||
- 모든 import 경로 수정 (28개 파일)
|
||||
- main.py의 skills import 경로 수정
|
||||
|
||||
**결과:**
|
||||
- 서비스 레이어 통합 완료
|
||||
- import 경로 일관성 확보
|
||||
|
||||
### Phase 2 Step 4: DB 접근 정리 (완료)
|
||||
|
||||
**수정 완료:**
|
||||
- feedback_handler.py: SessionLocal 제거, repository 사용
|
||||
- handle_chat_feedback를 async로 변경
|
||||
- update_or_create_feedback를 async로 변경 (내부 Session 관리)
|
||||
- get_conversation_by_id 추가
|
||||
- slack_handler.py: SessionLocal 제거, slack_repository 사용
|
||||
- get_team_uuid_by_slack_team_id 추가
|
||||
- 3곳의 SessionLocal 직접 호출 제거
|
||||
- intent_review_endpoint.py: FastAPI Depends 패턴 유지 (허용)
|
||||
|
||||
**결과:**
|
||||
- router/services에서 SessionLocal 직접 사용 제거
|
||||
- repository 패턴으로 통일
|
||||
- TDD 테스트 작성 및 통과 확인
|
||||
|
||||
### Phase 2: 계층 분리 리팩토링 (대규모)
|
||||
|
||||
#### Step 1: 엔드포인트 분리
|
||||
**생성:**
|
||||
- app/router/message_endpoint.py (~100줄)
|
||||
|
||||
**수정:**
|
||||
- main.py:84-176 제거 → message_endpoint.py로 이동
|
||||
- main.py에 include_router 추가
|
||||
|
||||
#### Step 2: 비즈니스 로직 분리
|
||||
**생성:**
|
||||
- app/services/message_service.py (~550줄)
|
||||
|
||||
**수정:**
|
||||
- router.py:128-682 (route_message) → message_service.py로 이동
|
||||
- router.py는 message_service 호출만 (~10줄)
|
||||
- slack_handler.py:424,437,448,893 - message_service 호출로 변경
|
||||
|
||||
#### Step 3: 폴더 구조 정리
|
||||
**이동:**
|
||||
- app/brain/ → app/services/brain/
|
||||
- app/llm/ → app/services/llm/
|
||||
- app/memory/ → app/services/memory/
|
||||
- app/skills/ → app/services/skills/
|
||||
- app/notifications/ → app/services/notifications/
|
||||
- app/pipelines/ → app/services/pipelines/
|
||||
|
||||
**생성:**
|
||||
- app/schemas/ - API 요청/응답 스키마 분리
|
||||
|
||||
#### Step 4: DB 접근 정리
|
||||
**생성:**
|
||||
- app/models/{domain}_model.py - ORM 모델 분리
|
||||
|
||||
**수정:**
|
||||
- state/database.py - models 제거, Repository만 남김
|
||||
- router/feedback_handler.py, intent_review_endpoint.py, slack_handler.py - state 통해서만 DB 접근
|
||||
- services 파일들 - asyncpg.connect() 제거, state 호출로 변경
|
||||
**필요 작업**:
|
||||
- state/repositories/ 폴더 생성
|
||||
- DB 접근 로직을 repositories로 분리
|
||||
- services에서 repositories 호출
|
||||
|
||||
---
|
||||
|
||||
## 기대효과
|
||||
## Phase 3: 폴더 구조 정리 (미착수)
|
||||
|
||||
### 즉시 효과
|
||||
- 중복 저장 버그 해결 → DB 용량 절약, 데이터 정합성 확보
|
||||
### 현재 구조
|
||||
```
|
||||
app/
|
||||
├── brain/ # services/brain/으로 이동 필요
|
||||
├── llm/ # services/llm/으로 이동 필요
|
||||
├── memory/ # services/memory/로 이동 필요
|
||||
├── router/ # routers/로 이름 변경 필요
|
||||
├── services/
|
||||
└── state/
|
||||
```
|
||||
|
||||
### 장기 효과
|
||||
1. **유지보수성**: 계층별 책임 명확 → 수정 영향 범위 최소화
|
||||
2. **확장성**: 새 기능 추가 시 어느 계층에 넣을지 명확
|
||||
3. **테스트**: 계층별 단위 테스트 작성 용이
|
||||
4. **협업**: 원칙 준수로 코드 가독성 향상
|
||||
5. **기술부채 감소**: 15개 위반 사항 해결
|
||||
### 목표 구조 (311_FastAPI_구조_원칙.md:28-42)
|
||||
```
|
||||
app/
|
||||
├── routers/ # 엔드포인트만
|
||||
├── services/ # 비즈니스 로직
|
||||
├── state/
|
||||
│ ├── models/ # ORM 모델
|
||||
│ └── repositories/ # DB 접근
|
||||
└── schemas/ # Pydantic 모델
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 구현 규모
|
||||
## Phase 4: 파일 크기 제한 (미착수)
|
||||
|
||||
### 최소 수정 (Phase 1만)
|
||||
- 영향 파일: 2개 (router.py, main.py)
|
||||
- 수정 라인: ~50줄
|
||||
- 소요 시간: 완료
|
||||
**초과 파일** (300줄 기준):
|
||||
- router.py: 915줄
|
||||
- main.py: 747줄
|
||||
- calendar_handler.py: 800줄+
|
||||
|
||||
### 전체 리팩토링 (Phase 2)
|
||||
- 영향 파일: 최소 30개
|
||||
- 신규 생성: ~650줄
|
||||
- 수정/제거: ~1,300줄
|
||||
- 총 변경: ~2,000줄
|
||||
- 예상 소요: 1-2일
|
||||
|
||||
---
|
||||
|
||||
## 우선순위
|
||||
|
||||
1. **긴급 (완료)**: 중복 저장 버그 해결
|
||||
2. **높음**: 엔드포인트 분리 (main.py 원칙 준수)
|
||||
3. **중간**: 비즈니스 로직 분리 (route_message → service)
|
||||
4. **낮음**: 폴더 구조 정리, 파일 크기 축소
|
||||
**분리 계획**:
|
||||
- 파일별 기능 단위로 분할
|
||||
- 단일 책임 원칙 적용
|
||||
|
||||
---
|
||||
|
||||
## 참고
|
||||
|
||||
- 311_FastAPI_구조_원칙.md - 계층 분리 원칙
|
||||
- 312_문서_작성_원칙.md - 문서 작성 규칙
|
||||
- `311_FastAPI_구조_원칙.md`
|
||||
- `troubleshooting/250924_대화저장_오류.md`
|
||||
- `troubleshooting/251123_rb8001_endpoint_service_separation.md`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user