DOCS/journey/troubleshooting/250817_email_skill_integration_status.md
Claude-51124 22557e7132 docs: 오래된 트러블슈팅 아카이브 및 구조 정리
- 7-8월 초기 구축 문서 12개를 _archive/troubleshooting/2025_07-08_initial_setup/로 이동
- book/300_architecture/390_human_in_the_loop_intent_learning.md를 journey/research/intent_classification/로 이동 (개발 여정 문서)
- 빈 폴더 제거 (journey/assets/*)
2025-11-17 14:06:05 +09:00

294 lines
7.2 KiB
Markdown

# Email Skill 통합 현황 및 TODO
## 작성일: 2025-08-13
## 작성자: Claude (with heejae)
---
## 1. 현재 상황
### 1.1 시스템 구조
```
User → rb8001 → skill-email → Gmail API
router.py가 라우팅
skill-email:8501
```
### 1.2 발생한 문제
**오류**: `422 Unprocessable Entity`
**원인 1**: Gmail OAuth 토큰 파일 없음
- 필요 위치: `/home/admin/auth-server/tokens/test_gmail.json`
- 현재 상태: 디렉토리는 있지만 토큰 파일 없음
**원인 2**: 잘못된 요청 형식
```python
# 현재 rb8001이 보내는 payload
{
"message": "이메일 보내줘",
"user_id": "U091UNVE41M",
"channel": "C123456",
"robeing_id": "rb8001",
"action": "compose",
"execution_plan": {...}
}
# skill-email이 기대하는 payload
{
"to": "recipient@example.com",
"subject": "제목",
"body": "내용",
"user_id": "test" # Gmail 토큰 식별용
}
```
---
## 2. 토큰 관리 방안
### 2.1 현재 구조 (파일 기반)
```yaml
위치: /home/admin/auth-server/tokens/
형식: {user_id}_gmail.json
내용:
- token: "access_token"
- refresh_token: "refresh_token"
- client_id: "xxx.apps.googleusercontent.com"
- client_secret: "secret"
- scopes: ["gmail.send", "gmail.readonly"]
```
### 2.2 제안: PostgreSQL 기반 관리
```sql
-- Gmail 토큰 테이블
CREATE TABLE gmail_token (
id SERIAL PRIMARY KEY,
user_id VARCHAR(100) UNIQUE NOT NULL,
robeing_id VARCHAR(50),
token TEXT NOT NULL,
refresh_token TEXT NOT NULL,
token_uri VARCHAR(255),
client_id VARCHAR(255),
client_secret VARCHAR(255),
scopes TEXT, -- JSON array
expiry TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**장점**:
- 중앙 집중식 관리
- 여러 서버에서 접근 가능
- 백업/복구 용이
- 토큰 갱신 이력 추적
---
## 3. 아키텍처 개선 방안
### 3.1 현재 문제점
- rb8001이 이메일 구조를 알아야 함
- skill-email이 자연어 처리 못함
- 여러 로빙이 하나의 skill-email 공유 시 충돌
### 3.2 개선안: 하이브리드 접근
```python
# rb8001 - 의도 파악만
async def route_email(message):
intent = extract_intent(message) # "이메일 보내줘"
hints = extract_entities(message) # {recipient_hint: "희재"}
return await skill_email.compose({
"original_message": message,
"hints": hints,
"slack_user_id": user_id,
"robeing_id": robeing_id
})
# skill-email - 이메일 생성 및 발송
async def compose_email(request):
# 1. 자연어에서 이메일 정보 추출
email_data = await parse_email_request(
request.original_message,
request.hints
)
# 2. 부족한 정보 확인
if not email_data.complete:
return {"need_info": ["recipient", "subject"]}
# 3. Gmail 토큰 확인
token = await get_token(request.slack_user_id)
if not token:
return {"error": "No Gmail token"}
# 4. 발송
return await send_via_gmail(email_data, token)
```
---
## 4. TODO List
### Phase 1: 긴급 수정 (토큰 없이 테스트)
- [ ] skill-email에 mock 모드 추가
```python
@app.post("/compose")
async def compose_mock(request):
# 이메일 구성만, 실제 발송 안 함
return {
"status": "composed",
"preview": {
"to": "test@example.com",
"subject": f"Re: {request.message[:30]}",
"body": f"About: {request.message}"
},
"error": "No Gmail token (mock mode)"
}
```
- [ ] rb8001 router 수정
- 이메일 intent 감지 로직 개선
- skill-email로 원본 메시지 전달
### Phase 2: 토큰 시스템 구축
- [ ] Gmail OAuth 플로우 구현
```python
# auth-server에 추가
@app.get("/oauth/gmail/authorize")
async def gmail_auth():
# Google OAuth URL 생성
return redirect(google_oauth_url)
@app.get("/oauth/gmail/callback")
async def gmail_callback(code: str):
# 토큰 교환 및 저장
token = exchange_code_for_token(code)
save_token_to_db(token)
```
- [ ] PostgreSQL 토큰 테이블 생성
- [ ] robeing-state-service에 토큰 API 추가
### Phase 3: 사용자 매핑
- [ ] Slack User ID → Gmail 계정 매핑
```python
USER_GMAIL_MAPPING = {
"U0925SXQFDK": "heejae", # 희재
"U091UNVE41M": "jongtae", # 종태
"U092HQMUXMB": "hanyong" # 한용
}
```
- [ ] 로빙별 발신 계정 설정
```python
ROBEING_GMAIL_MAPPING = {
"rb8001": "robeing.main",
"rb10408": "robeing.test",
"rb10508": "robeing.micro"
}
```
### Phase 4: LLM 통합
- [ ] skill-email에 경량 LLM 추가 (선택사항)
- [ ] 이메일 템플릿 시스템
- [ ] 주소록 검색 기능
### Phase 5: 프로덕션 준비
- [ ] 토큰 자동 갱신 (refresh_token 사용)
- [ ] 에러 처리 및 재시도 로직
- [ ] 발송 이력 저장
- [ ] 보안 검토 (토큰 암호화)
---
## 5. 서버 정보
### Auth 서버 (PostgreSQL 위치)
```bash
Host: 124.55.18.179
Port: 51123
User: heejae
Database: main_db
PostgreSQL User: robeings/robeings
```
### SSH 터널 (현재 설정)
```bash
# 로컬 5433 → 원격 5432
ssh -L 5433:localhost:5432 admin@124.55.18.179 -p 51123
```
### 관련 서비스
- **rb8001**: 메인 로빙 (포트 8001)
- **skill-email**: 이메일 스킬 (포트 8501)
- **robeing-state-service**: 상태 관리 (포트 8507)
---
## 6. 결정 필요 사항
### Q1: LLM 위치
- **Option A**: skill-email에 자체 LLM (독립적)
- **Option B**: rb8001에서 처리 후 전달 (중앙 집중)
- **Option C**: 하이브리드 (기본은 rb8001, 복잡한 경우 skill-email) ✅
### Q2: 토큰 저장 방식
- **Option A**: 파일 시스템 (현재)
- **Option B**: PostgreSQL (제안) ✅
- **Option C**: Redis (임시 캐시)
### Q3: 멀티 로빙 지원
- 각 로빙이 다른 Gmail 계정 사용?
- 사용자별로 Gmail 계정 매핑?
- 공용 계정 하나로 통합?
---
## 7. 참고 코드
### 현재 skill-email 구조
```python
# main.py
TOKEN_BASE = os.getenv("TOKEN_BASE", "../auth-server/tokens")
# services/gmail_service.py
class FileCredentialsProvider:
def get_credentials(self, user_id: str):
token_path = self.token_base / f"{user_id}_gmail.json"
# ... 토큰 로드
```
### 제안하는 DB 기반 구조
```python
class DBCredentialsProvider:
async def get_credentials(self, user_id: str):
# PostgreSQL에서 토큰 조회
token = await db.query(
"SELECT * FROM gmail_token WHERE user_id = %s",
user_id
)
return Credentials(**token)
```
---
## 8. 다음 단계
1. **즉시 실행**: Mock 모드로 이메일 스킬 테스트
2. **단기 과제**: Gmail OAuth 토큰 생성 및 저장
3. **중기 과제**: PostgreSQL 기반 토큰 관리 시스템
4. **장기 과제**: 멀티 로빙 및 사용자별 계정 관리
---
## 로그 예시
```
INFO: Calling service: http://172.17.0.1:8501/send
with payload keys: ['message', 'user_id', 'channel', 'robeing_id', 'action', 'execution_plan']
ERROR: 422 Unprocessable Entity
DETAIL: 토큰 파일이 없습니다: /app/auth-server/tokens/test_gmail.json
```