docs: troubleshooting 문서 통합 (plans 삭제, 102줄)
- plans/251014_claude_coldmail_hybrid_implementation.md 삭제 - troubleshooting/251014_claude_coldmail_filter_tokenization_issue.md 통합 - 문제 분석 + 해결 방안 + 구현 계획 + 교훈 (102줄) - 코드 블록 최소화, 파일명:줄번호로 참조 - 구현 완료 섹션 추가 (커밋 7c5b033) 문서 작성 원칙 준수 (100줄 이하, 주제별 분리 대신 축약) Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d26bada28b
commit
4dc8bf3e4c
@ -1,138 +0,0 @@
|
|||||||
# Coldmail 하이브리드 필터 구현 계획
|
|
||||||
|
|
||||||
**날짜**: 2025-10-14
|
|
||||||
**작성자**: Claude (51124 서버 전담)
|
|
||||||
**관련**: `251014_claude_coldmail_filter_tokenization_issue.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 구현 개요
|
|
||||||
|
|
||||||
**목표**: KoBERT + Gemini + Naive Bayes 3단계 하이브리드 coldmail 필터
|
|
||||||
**예상 시간**: 12시간 (1.5일)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 1: KoBERT 임베딩 필터 (4시간)
|
|
||||||
|
|
||||||
### 신규 파일
|
|
||||||
- `rb8001/app/services/coldmail_embedding_filter.py`
|
|
||||||
|
|
||||||
### 주요 함수
|
|
||||||
- `create_embedding()`: skill-embedding (8515)로 임베딩 생성
|
|
||||||
- `calculate_similarity()`: cosine similarity 계산
|
|
||||||
- `is_coldmail_by_embedding()`: threshold 0.6 기반 판단
|
|
||||||
|
|
||||||
### DB 테이블
|
|
||||||
```sql
|
|
||||||
CREATE TABLE coldmail_embedding_clusters (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
embedding VECTOR(768),
|
|
||||||
label VARCHAR(10),
|
|
||||||
example_subject TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT NOW()
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**필요 확장**: pgvector extension (`CREATE EXTENSION vector;`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 2: Gemini LLM 분류기 (2시간)
|
|
||||||
|
|
||||||
### 신규 파일
|
|
||||||
- `rb8001/app/services/coldmail_llm_classifier.py`
|
|
||||||
|
|
||||||
### 주요 함수
|
|
||||||
- `classify_by_llm()`: Gemini zero-shot 분류 + 이유 설명
|
|
||||||
|
|
||||||
### Prompt 예시
|
|
||||||
```
|
|
||||||
다음 이메일이 투자/제안/협업 관련 coldmail인지 판단하시오.
|
|
||||||
제목: {subject}
|
|
||||||
발신자: {sender_email}
|
|
||||||
|
|
||||||
응답: {"is_coldmail": true/false, "reason": "..."}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 주의사항
|
|
||||||
- JSON 마크다운 블록(```json) 제거 필요
|
|
||||||
- gemini-2.5-flash-lite 사용 (비용 최소화)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 3: 하이브리드 통합 (3시간)
|
|
||||||
|
|
||||||
### 신규 파일
|
|
||||||
- `rb8001/app/services/coldmail_hybrid_filter.py`
|
|
||||||
|
|
||||||
### 주요 함수
|
|
||||||
- `hybrid_coldmail_filter()`: 3단계 순차 실행
|
|
||||||
- `update_from_feedback()`: Slack 버튼 피드백 처리
|
|
||||||
|
|
||||||
### 반환 구조
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"stage": "embedding" | "llm" | "hybrid",
|
|
||||||
"embedding_similarity": 0.75,
|
|
||||||
"llm_reason": "투자 유치 관련 IR 자료",
|
|
||||||
"naive_bayes_score": 0.85
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 4: coldmail_briefing 통합 (1시간)
|
|
||||||
|
|
||||||
### 수정 파일
|
|
||||||
- coldmail_briefing.py:121-136
|
|
||||||
|
|
||||||
### 변경 내용
|
|
||||||
- 기존 `is_coldmail()` → `hybrid_coldmail_filter()` 교체
|
|
||||||
- 로그에 분류 상세 정보 추가
|
|
||||||
- `email["coldmail_details"]` 저장
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 5: 테스트 및 검증 (2시간)
|
|
||||||
|
|
||||||
### 테스트 케이스
|
|
||||||
|
|
||||||
**Success (coldmail)**:
|
|
||||||
1. "251013_올굿즈컴퍼니_회사소개서.pdf" from gomtose@naver.com
|
|
||||||
2. "2025 빅웨이브 하반기 IR 초대" from biigwave@ccei.kr
|
|
||||||
3. "투자제안서 검토 요청" from silkro2009@silkro.org
|
|
||||||
|
|
||||||
**Failure (normal)**:
|
|
||||||
1. "[KBAN] 피싱 메일 주의" from jointips@kban.or.kr
|
|
||||||
2. "[SSG.COM] 주문 내역" from ssgadmin@ssg.com
|
|
||||||
3. "회의 일정 안내" from team@company.com
|
|
||||||
|
|
||||||
### 검증 항목
|
|
||||||
- [ ] 임베딩 클러스터 로딩 성공
|
|
||||||
- [ ] LLM JSON 파싱 정상
|
|
||||||
- [ ] Slack 피드백 → DB 반영 확인
|
|
||||||
- [ ] 응답 시간 500ms 이내
|
|
||||||
- [ ] API 호출 로그 확인 (10% 이하)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 구현 일정
|
|
||||||
|
|
||||||
| Phase | 작업 | 시간 | 우선순위 |
|
|
||||||
|-------|------|------|---------|
|
|
||||||
| 1 | KoBERT 임베딩 필터 | 4h | 높음 |
|
|
||||||
| 2 | Gemini LLM 분류기 | 2h | 중간 |
|
|
||||||
| 3 | 하이브리드 통합 | 3h | 높음 |
|
|
||||||
| 4 | coldmail_briefing 통합 | 1h | 높음 |
|
|
||||||
| 5 | 테스트 및 검증 | 2h | 높음 |
|
|
||||||
|
|
||||||
**총 12시간** (1.5일)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 참고
|
|
||||||
|
|
||||||
- 2024년 연구: BERT F1 0.99 (Journal of Big Data)
|
|
||||||
- KoBERT: github.com/SKTBrain/KoBERT
|
|
||||||
- skill-embedding: localhost:8515
|
|
||||||
@ -1,113 +1,88 @@
|
|||||||
# Coldmail 필터 토큰화 문제 분석
|
# Coldmail 필터 토큰화 문제 및 하이브리드 구현
|
||||||
|
|
||||||
**날짜**: 2025-10-14
|
**날짜**: 2025-10-14
|
||||||
**작성자**: Claude (51124 서버 전담)
|
**작성자**: Claude (51124 서버 전담)
|
||||||
**관련 파일**: `rb8001/app/services/coldmail_filter.py`, `rb8001/app/scheduler/jobs/coldmail_briefing.py`
|
**관련 파일**: `rb8001/app/services/coldmail_filter.py`, `rb8001/tests/test_coldmail_filter.py`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 문제 상황
|
## 문제 상황
|
||||||
|
|
||||||
### 발단
|
|
||||||
9시 5분 Coldmail Daily Briefing 실행 시 IR deck 이메일이 Slack에 전송되지 않음.
|
9시 5분 Coldmail Daily Briefing 실행 시 IR deck 이메일이 Slack에 전송되지 않음.
|
||||||
|
|
||||||
### 조사 결과
|
**조사 결과**:
|
||||||
- 올굿즈컴퍼니 IR deck (`251013_올굿즈컴퍼니_회사소개서.pdf`): coldmail 확률 30.35%
|
- 올굿즈컴퍼니 IR deck: coldmail 확률 30.35% (threshold 70% 미달)
|
||||||
- 빅웨이브 IR 행사 안내: coldmail 확률 7.65%
|
- 빅웨이브 IR 행사: 7.65%
|
||||||
- 투자제안서 검토 요청: coldmail 확률 28.38%
|
- 투자제안서 검토: 28.38%
|
||||||
- **모두 threshold 70% 미달로 필터링 실패**
|
|
||||||
|
|
||||||
---
|
**근본 원인**: coldmail_filter.py:44-52 정규식 토큰화 실패
|
||||||
|
- "회사소개서" → 하나의 토큰 (형태소 분리 안 됨)
|
||||||
|
- "ir에" → 하나의 토큰 (조사 분리 안 됨)
|
||||||
|
- DB의 "ir" (cold 90.9%)와 매칭 실패
|
||||||
|
|
||||||
## 근본 원인: 토큰화 알고리즘 한계
|
**DB 학습 데이터는 정상** (56단어, cold 347, normal 210)
|
||||||
|
|
||||||
### 현재 구현
|
|
||||||
coldmail_filter.py:44-52 - 정규식 `re.split(r'[\s\W]+')` 사용
|
|
||||||
|
|
||||||
### 문제점
|
|
||||||
|
|
||||||
**1. 복합명사 분해 실패**
|
|
||||||
- `"회사소개서"` → 하나의 토큰 (형태소 분리 안 됨)
|
|
||||||
- DB에는 "회사", "소개서"로 학습되어 있으나 매칭 실패
|
|
||||||
|
|
||||||
**2. 조사 분리 실패**
|
|
||||||
- `"ir에"` → 하나의 토큰
|
|
||||||
- DB의 "ir" (coldmail 90.9%)와 매칭 실패
|
|
||||||
|
|
||||||
**3. 테스트 결과**
|
|
||||||
```
|
|
||||||
제목: "251013_올굿즈컴퍼니_회사소개서.pdf"
|
|
||||||
토큰: ['251013_올굿즈컴퍼니_회사소개서', 'pdf']
|
|
||||||
|
|
||||||
제목: "2025 빅웨이브 하반기 IR에 여러분을 초대합니다"
|
|
||||||
토큰: ['2025', '빅웨이브', 'biig', 'wave', '하반기', 'ir에', ...]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 학습 데이터는 정상
|
|
||||||
```
|
|
||||||
총 단어 수: 56
|
|
||||||
총 coldmail: 347, normal: 210
|
|
||||||
주요 키워드: ir(90.9%), 투자(90.9%), 투자유치(100%), 제안(80%)
|
|
||||||
```
|
|
||||||
|
|
||||||
**테스트 스크립트**: `rb8001/tests/test_coldmail_filter.py`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 해결 방안: 3단계 하이브리드
|
## 해결 방안: 3단계 하이브리드
|
||||||
|
|
||||||
### 설계 철학
|
**설계**:
|
||||||
- **속도**: KoBERT 임베딩으로 1차 필터링 (빠름, 90% 처리)
|
1. KoBERT 임베딩 (고속, 90% 필터링) - skill-embedding (8515) 활용
|
||||||
- **정확도**: Gemini LLM으로 경계선 케이스 판단 (높음, 10% 처리)
|
2. Gemini LLM (정밀, 10% 처리) - zero-shot classification
|
||||||
- **학습**: Naive Bayes로 실시간 피드백 반영 (지속 개선)
|
3. Naive Bayes (실시간 학습) - Slack 피드백 반영
|
||||||
|
|
||||||
### 아키텍처
|
**예상 효과**:
|
||||||
```
|
- 재현율: 30% → 95%
|
||||||
이메일 수신
|
- 응답 속도: 500ms (1차 100ms + 2차 400ms)
|
||||||
↓
|
- API 비용: 10% (1차 필터 효과)
|
||||||
[1단계] KoBERT 임베딩 (고속)
|
|
||||||
- skill-embedding (8515) 활용
|
|
||||||
- coldmail/normal 클러스터와 cosine similarity
|
|
||||||
- threshold 0.6 이상만 통과
|
|
||||||
↓
|
|
||||||
[2단계] Gemini LLM (정밀)
|
|
||||||
- 1단계 통과한 10%만 처리
|
|
||||||
- Zero-shot classification + 이유 설명
|
|
||||||
↓
|
|
||||||
[3단계] Naive Bayes (학습)
|
|
||||||
- Slack 버튼 피드백 → DB 즉시 반영
|
|
||||||
- 1단계 threshold 동적 조정
|
|
||||||
↓
|
|
||||||
Slack Lists 등록 + 피드백 버튼
|
|
||||||
```
|
|
||||||
|
|
||||||
**상세 구현**: `251014_claude_coldmail_hybrid_implementation.md` 참고
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 기술 선택 근거
|
## 구현 계획 (12시간)
|
||||||
|
|
||||||
### KoBERT (2024년 F1 0.99)
|
### Phase 1: KoBERT 임베딩 필터 (4h)
|
||||||
- 의미 기반 매칭 (동의어, 유사 표현 자동 처리)
|
**신규**: coldmail_embedding_filter.py
|
||||||
- skill-embedding (8515) 기존 인프라 활용
|
- create_embedding(): skill-embedding (8515)로 임베딩 생성
|
||||||
- 형태소 분석기 불필요
|
- calculate_similarity(): cosine similarity
|
||||||
|
- is_coldmail_by_embedding(): threshold 0.6
|
||||||
|
|
||||||
### Gemini LLM
|
**DB**: coldmail_embedding_clusters 테이블 (pgvector extension 필요)
|
||||||
- 즉시 사용 가능 (학습 데이터 불필요)
|
|
||||||
- 설명 가능성 (판단 이유 제공)
|
|
||||||
- 1차 필터 후 10%만 호출 → 비용 감당 가능
|
|
||||||
|
|
||||||
### Naive Bayes 유지
|
### Phase 2: Gemini LLM 분류기 (2h)
|
||||||
- 실시간 학습 가능 (Slack 피드백 → DB 즉시 반영)
|
**신규**: coldmail_llm_classifier.py
|
||||||
- 빠른 추론 (임베딩/LLM 장애 시 백업)
|
- classify_by_llm(): Gemini zero-shot + 이유 설명
|
||||||
|
- JSON 마크다운 블록 전처리
|
||||||
|
|
||||||
|
### Phase 3: 하이브리드 통합 (3h)
|
||||||
|
**신규**: coldmail_hybrid_filter.py
|
||||||
|
- hybrid_coldmail_filter(): 3단계 순차 실행
|
||||||
|
- update_from_feedback(): Slack 버튼 → 3모델 업데이트
|
||||||
|
|
||||||
|
### Phase 4: coldmail_briefing 통합 (1h)
|
||||||
|
**수정**: coldmail_briefing.py:121-136
|
||||||
|
- is_coldmail() → hybrid_coldmail_filter() 교체
|
||||||
|
- 분류 상세 정보 로그 추가
|
||||||
|
|
||||||
|
### Phase 5: 테스트 및 검증 (2h)
|
||||||
|
**테스트 케이스** (6개):
|
||||||
|
- Success: 올굿즈컴퍼니, 빅웨이브, 투자제안서
|
||||||
|
- Failure: KBAN, SSG.COM, 회의 안내
|
||||||
|
|
||||||
|
**검증**:
|
||||||
|
- [ ] 임베딩 클러스터 로딩
|
||||||
|
- [ ] LLM JSON 파싱
|
||||||
|
- [ ] Slack 피드백 → DB 반영
|
||||||
|
- [ ] 응답 시간 500ms 이내
|
||||||
|
- [ ] API 호출 10% 이하
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 예상 효과
|
## 구현 완료
|
||||||
|
|
||||||
- **재현율**: 30% → 95% (IR deck 놓치지 않음)
|
### 테스트 스크립트 (커밋 7c5b033)
|
||||||
- **응답 속도**: 평균 500ms (1차 100ms + 2차 400ms)
|
**파일**: rb8001/tests/test_coldmail_filter.py
|
||||||
- **API 비용**: 전체의 10%만 호출 (1차 필터 효과)
|
- test_hybrid_coldmail_filter():92-134 추가
|
||||||
|
- --test hybrid 옵션 추가
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -115,14 +90,12 @@ Slack Lists 등록 + 피드백 버튼
|
|||||||
|
|
||||||
### 단순 정규식 토큰화의 한계
|
### 단순 정규식 토큰화의 한계
|
||||||
- 한국어는 교착어 특성상 조사/어미 분리 필수
|
- 한국어는 교착어 특성상 조사/어미 분리 필수
|
||||||
- 복합명사 분해 없이는 키워드 매칭 실패
|
|
||||||
- 교훈: 의미 기반 접근(임베딩)이 토큰 기반보다 강건함
|
- 교훈: 의미 기반 접근(임베딩)이 토큰 기반보다 강건함
|
||||||
|
|
||||||
### 단일 모델의 위험성
|
### 단일 모델의 위험성
|
||||||
- Naive Bayes만으로는 형태소 문제 해결 불가
|
- Naive Bayes: 형태소 문제 해결 불가
|
||||||
- LLM만으로는 학습 불가능 + 비용 과다
|
- LLM: 학습 불가능 + 비용 과다
|
||||||
- 교훈: 하이브리드 접근으로 각 모델의 장점 활용
|
- 교훈: 하이브리드 접근으로 각 모델의 장점 활용
|
||||||
|
|
||||||
### 피드백 루프의 중요성
|
### 피드백 루프의 중요성
|
||||||
- 모델 정확도는 사용자 피드백으로 지속 개선
|
|
||||||
- 교훈: 실시간 학습 가능한 구조가 장기적으로 유리
|
- 교훈: 실시간 학습 가능한 구조가 장기적으로 유리
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user