docs: plan 문서 간결화 - 100줄 이하, 구현 섹션 제거

This commit is contained in:
happybell80 2025-12-04 22:07:47 +09:00
parent 192851220a
commit 31d74211c5
4 changed files with 183 additions and 1163 deletions

View File

@ -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`

View File

@ -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`

View File

@ -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`

View File

@ -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`