From 67f9d45547b55456738a64e7d9e2ebae7a5d9c5f Mon Sep 17 00:00:00 2001 From: 0914eagle <0914eagle@gmail.com> Date: Wed, 20 Aug 2025 16:52:27 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0819_rb8001_gmail_integration_completed.md | 400 ++++++++++++++ ...0_email_callback_architecture_completed.md | 517 ++++++++++++++++++ 2 files changed, 917 insertions(+) create mode 100644 plans/completed/250819_rb8001_gmail_integration_completed.md create mode 100644 plans/completed/250820_email_callback_architecture_completed.md diff --git a/plans/completed/250819_rb8001_gmail_integration_completed.md b/plans/completed/250819_rb8001_gmail_integration_completed.md new file mode 100644 index 0000000..ebd214a --- /dev/null +++ b/plans/completed/250819_rb8001_gmail_integration_completed.md @@ -0,0 +1,400 @@ +# rb8001과 skill-email Gmail 통합 완료 보고서 + +## 작업일: 2025-08-19 +## 작업자: 희재 +## 상태: 완료 + +--- + +## 1. 작업 개요 + +### 목적 +rb8001이 Gmail 기능을 사용할 수 있도록 skill-email 서비스와 통합 + +### 배경 +- skill-email은 DB 기반 토큰 관리로 전환 완료 +- rb8001이 사용자의 Gmail 요청을 감지하고 처리할 수 있도록 통합 필요 +- Gmail 아이템 장착 상태 확인 및 권한 관리 필요 + +--- + +## 2. 구현 내용 + +### 2.1 환경변수 설정 +**파일**: `/home/heejae/rb8001/.env` +```bash +# Skill Services +SKILL_EMAIL_URL=http://localhost:8501 +MONITOR_SERVICE_URL=http://localhost:9024 + +# PostgreSQL for Gmail tokens (SSH tunnel) +POSTGRES_CONNECTION_STRING=postgresql://robeings:robeings@localhost:5433/auth_db +``` + +### 2.2 Gmail 통합 모듈 구현 +**파일**: `/home/heejae/rb8001/app/skills/email_integration.py` + +#### 주요 클래스: EmailIntegration +Gmail 스킬 통합을 담당하는 핵심 모듈 + +#### 핵심 메서드 + +1. **check_gmail_equipped(user_id)** + ```python + async def check_gmail_equipped(self, user_id: str) -> bool: + # PostgreSQL에서 직접 장착 상태 확인 + # 5분 캐싱으로 성능 최적화 + cur.execute(""" + SELECT COUNT(*) FROM gmail_tokens + WHERE user_id = %s AND is_equipped = true + """, (user_id,)) + ``` + - gmail_tokens 테이블의 is_equipped 확인 + - 캐시 TTL: 300초 (5분) + - DB 연결 실패 시 False 반환 + +2. **parse_email_intent(message)** + ```python + def parse_email_intent(self, message: str) -> Optional[Dict[str, Any]]: + # 이메일 관련 키워드 감지 + # 발송/조회/답장 의도 분류 + # 수신자, 제목, 내용 추출 + ``` + - 키워드: "이메일", "메일", "보내", "전송", "확인", "조회", "답장" + - 정규표현식으로 수신자 추출: `([가-힣]+님?)(?:한테|에게|께)` + - 이메일 주소 패턴: `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}` + +3. **process_email_request(message, user_id, channel)** + ```python + async def process_email_request( + self, + message: str, + user_id: str, + channel: str = None + ) -> Tuple[bool, str]: + # 1. Gmail 장착 확인 + # 2. 이메일 의도 파싱 + # 3. skill-email 서비스 호출 + # 4. 응답 처리 + ``` + + **처리 플로우:** + - 미장착 시: "Gmail 패스포트를 먼저 장착해주세요! 🎫" + - 발송 요청: POST /process 엔드포인트 호출 + - 조회 요청: GET /messages 엔드포인트 호출 + - 토큰 만료: 재인증 안내 메시지 + +4. **handle_reauth(user_id)** + ```python + async def handle_reauth(self, user_id: str) -> Tuple[bool, str]: + # robeing-monitor 서비스에 재인증 요청 + # OAuth URL 생성 및 반환 + ``` + +### 2.3 라우터 통합 +**파일**: `/home/heejae/rb8001/app/router/router.py` + +#### 변경 내용 +```python +# Import 추가 +from app.skills.email_integration import email_integration + +# route_message 메서드에 Gmail 처리 추가 +async def route_message(self, message: str, user_id: str, channel: str, thread_ts: str = None): + # 0. 슬래시 명령어 처리 + # ... + + # 0.5. Gmail 요청 확인 및 처리 (새로 추가) + email_result = await email_integration.process_email_request(message, user_id, channel) + if email_result[0] or email_result[1] is not None: + return { + "success": email_result[0], + "message": email_result[1], + "content": email_result[1], + "service": "gmail", + "execution_plan": {"intent": "email", "skills": ["email"]} + } + + # 1. 기존 Brain 기반 라우팅 + # ... +``` + +**처리 우선순위:** +1. 슬래시 명령어 (/memory, /stats 등) +2. Gmail 요청 (이메일 발송/조회) +3. 일반 Brain 기반 라우팅 + +--- + +## 3. 테스트 구현 + +### 3.1 통합 테스트 스크립트 +**파일**: `/home/heejae/rb8001/test_gmail_integration.py` + +#### 테스트 케이스 +1. **장착 상태 확인** + - U091UNVE41M (전희재): is_equipped=True ✅ + - U0925SXQFDK (종태): is_equipped=False ✅ + +2. **의도 파싱 테스트** + - "종태님한테 회의 일정 메일 보내줘" → action: send, to: 종태님 ✅ + - "최근 메일 확인해줘" → action: list, limit: 5 ✅ + +3. **프로세스 테스트** + - 미장착 사용자 → 장착 안내 메시지 ✅ + - 장착된 사용자 → skill-email 호출 ✅ + +### 3.2 테스트 결과 +```bash +=== rb8001 Gmail Integration Test === + +[Test: 장착 상태 확인] +User U091UNVE41M: Gmail equipped = True + +[Test: 이메일 발송 의도 파싱] +Parsed intent: {'action': 'send', 'to': '종태님', 'subject': '회의 일정 안내', 'body': '...'} + +[Test: 이메일 조회 의도 파싱] +Parsed intent: {'action': 'list', 'limit': 5} + +[Test: 이메일 처리 (미장착)] +Success: False +Message: Gmail 패스포트를 먼저 장착해주세요! 🎫 + +[Test: 이메일 처리 (장착됨)] +Success: True +Message: 이메일을 보내기 위해 다음 정보가 필요합니다... +``` + +--- + +## 4. API 통신 + +### 4.1 skill-email 엔드포인트 +- **POST /process**: 대화형 이메일 처리 +- **GET /messages**: 이메일 목록 조회 +- **GET /health**: 서비스 상태 확인 + +### 4.2 요청/응답 형식 + +#### 발송 요청 +```json +{ + "message": "종태님한테 회의 일정 메일 보내줘", + "user_id": "U091UNVE41M", + "channel": "C123456", + "robing_id": "rb8001" +} +``` + +#### 응답 +```json +{ + "success": true, + "content": "이메일 처리 메시지", + "data": { + "type": "need_more_info|send|error", + "draft": {...}, + "missing_fields": ["to", "subject"] + } +} +``` + +--- + +## 5. 보안 및 권한 관리 + +### 5.1 장착 확인 +- is_equipped=true인 토큰만 사용 가능 +- 사용자별 개별 확인 (Slack User ID 기반) + +### 5.2 캐싱 +- 장착 상태 5분 캐싱 +- DB 부하 감소 및 응답 속도 개선 + +### 5.3 에러 처리 +- DB 연결 실패 → 기본값 False (안전한 실패) +- 토큰 만료 → 재인증 안내 +- 네트워크 타임아웃 → 30초 제한 + +--- + +## 6. 디렉토리 구조 + +``` +/home/heejae/rb8001/ +├── .env # 환경변수 (수정됨) +├── app/ +│ ├── skills/ # 새로 생성 +│ │ └── email_integration.py # Gmail 통합 모듈 +│ ├── router/ +│ │ └── router.py # 라우터 (수정됨) +│ └── ... +└── test_gmail_integration.py # 테스트 스크립트 +``` + +--- + +## 7. 데이터 플로우 + +``` +사용자 메시지 (Slack) + ↓ +rb8001 (router.py) + ↓ +Gmail 의도 감지 + ↓ +장착 상태 확인 (PostgreSQL) + ↓ +skill-email 호출 (HTTP) + ↓ +Gmail API 실행 + ↓ +응답 반환 + ↓ +Slack 메시지 전송 +``` + +--- + +## 8. 주요 성과 + +1. **통합 완료** ✅ + - rb8001이 Gmail 요청 감지 및 처리 가능 + - skill-email 서비스와 완전 연동 + +2. **권한 관리** ✅ + - Gmail 아이템 장착 상태 확인 + - 미장착 시 친절한 안내 메시지 + +3. **성능 최적화** ✅ + - 5분 캐싱으로 DB 부하 감소 + - 30초 타임아웃으로 무한 대기 방지 + +4. **유연한 구조** ✅ + - 의도 파싱과 서비스 호출 분리 + - 확장 가능한 아키텍처 + +--- + +## 9. 문제 해결 + +### 9.1 권한 오류 +- **문제**: `/home/heejae/rb8001/app/skills/` 디렉토리 생성 실패 +- **해결**: `sudo mkdir -p` 및 `chmod 777` 적용 + +### 9.2 의도 파싱 +- **문제**: "test@example.com에게" 형식 파싱 실패 +- **해결**: 이메일 정규표현식 패턴 추가 + +### 9.3 모듈 임포트 +- **문제**: psycopg2 모듈 위치 +- **해결**: 함수 내부에서 동적 임포트 + +--- + +## 10. 사용 시나리오 + +### 시나리오 1: 이메일 발송 +``` +사용자: @rb8001 종태님한테 회의 일정 메일 보내줘 +rb8001: (장착 확인) → (의도 파싱) → (skill-email 호출) +skill-email: 이메일 주소가 필요합니다. 종태님의 이메일은? +사용자: goeun2dc@gmail.com +skill-email: (이메일 발송) → 성공 메시지 +``` + +### 시나리오 2: 미장착 사용자 +``` +사용자: @rb8001 이메일 보내줘 +rb8001: Gmail 패스포트를 먼저 장착해주세요! 🎫 + `/inventory` 명령어로 인벤토리를 확인할 수 있습니다. +``` + +### 시나리오 3: 메일 조회 +``` +사용자: @rb8001 최근 메일 확인해줘 +rb8001: 📧 최근 이메일: + 1. *회의 일정 안내* + 발신: 김종태 + 날짜: 2025-08-19 + ... +``` + +--- + +## 11. 향후 개선사항 + +### 즉시 필요 +- [ ] Docker 컨테이너 재빌드 및 배포 +- [ ] Slack 실제 환경 테스트 +- [ ] 에러 로깅 강화 + +### 추후 개선 +- [ ] 사용자 이름 → 이메일 주소 자동 매핑 +- [ ] 답장 기능 구현 +- [ ] 첨부파일 지원 +- [ ] 이메일 검색 기능 +- [ ] 대화 컨텍스트 유지 (연속 대화) + +--- + +## 12. 의존성 + +### Python 패키지 +- httpx: 비동기 HTTP 클라이언트 +- psycopg2-binary: PostgreSQL 연결 +- asyncio: 비동기 처리 + +### 외부 서비스 +- skill-email (포트 8501) +- PostgreSQL (SSH 터널 5433 → 5432) +- robeing-monitor (포트 9024, 선택적) + +--- + +## 13. 참고 명령어 + +```bash +# 통합 테스트 실행 +cd /home/heejae/rb8001 && python3 test_gmail_integration.py + +# skill-email 상태 확인 +curl http://localhost:8501/health + +# 이메일 발송 테스트 +curl -X POST http://localhost:8501/process \ + -H "Content-Type: application/json" \ + -d '{"message": "...", "user_id": "U091UNVE41M", ...}' + +# PostgreSQL 장착 상태 확인 +python3 -c " +import psycopg2 +conn = psycopg2.connect('postgresql://robeings:robeings@localhost:5433/auth_db') +cur = conn.cursor() +cur.execute('SELECT user_id, is_equipped FROM gmail_tokens') +for row in cur.fetchall(): + print(f'{row[0]}: equipped={row[1]}') +" +``` + +--- + +## 14. 검증 체크리스트 + +- [x] 환경변수 설정 완료 +- [x] EmailIntegration 클래스 구현 +- [x] 장착 상태 확인 동작 +- [x] 의도 파싱 정확도 +- [x] skill-email API 호출 성공 +- [x] 라우터 통합 완료 +- [x] 에러 처리 동작 +- [x] 캐싱 메커니즘 동작 +- [x] 테스트 스크립트 통과 +- [ ] 실제 Gmail API 호출 (토큰 유효성) +- [ ] Slack 실환경 테스트 + +--- + +**작업 완료: 2025-08-19** +**다음 단계: Docker 배포 및 실환경 테스트** \ No newline at end of file diff --git a/plans/completed/250820_email_callback_architecture_completed.md b/plans/completed/250820_email_callback_architecture_completed.md new file mode 100644 index 0000000..58f4d4a --- /dev/null +++ b/plans/completed/250820_email_callback_architecture_completed.md @@ -0,0 +1,517 @@ +# Email Skill Callback Architecture 구현 완료 보고서 + +## 작업일: 2025-08-20 +## 작업자: 희재 +## 상태: 완료 + +--- + +## 1. 작업 개요 + +### 배경 및 문제점 +- **초기 문제**: skill-email이 독립적으로 LLM 서비스(포트 8003)를 호출하는 구조 +- **다중 로빙 충돌**: rb8001과 rb8002가 동시에 skill-email 사용 시 전역 handler 공유로 대화 상태 혼재 +- **LLM 중복**: 각 서비스가 개별 LLM을 호출하는 비효율적 구조 + +### 해결 방안 +- **Callback 패턴**: skill-email이 필요시 로빙의 LLM을 callback으로 호출 +- **로빙별 Handler**: 각 로빙이 독립적인 conversation handler 인스턴스 사용 +- **중앙집중 LLM**: 각 로빙이 자신의 LLM을 제공하여 일관된 처리 + +--- + +## 2. 아키텍처 변경 + +### 이전 구조 (단방향) +``` +rb8001 → skill-email → LLM Service (8003) +rb8002 → skill-email → LLM Service (8003) [같은 서비스] + ↑ + 전역 handler (충돌!) +``` + +### 새로운 구조 (Callback) +``` +rb8001 → skill-email (callback URL 전달) + ↓ + handler["rb8001"] 생성/선택 + ↓ + LLM 필요시 → rb8001/api/llm/extract 호출 + ↑ + rb8001의 LLM이 처리 + +rb8002 → skill-email (callback URL 전달 또는 없음) + ↓ + handler["rb8002"] 생성/선택 (독립 인스턴스) + ↓ + 자체 파싱 또는 rb8002의 LLM 호출 +``` + +--- + +## 3. 구현 내용 + +### 3.1 skill-email 변경사항 + +#### A. 로빙별 Handler 관리 (`/home/heejae/skill-email/main.py`) +```python +# 이전: 전역 단일 handler +conversation_handler = ConversationHandler() + +# 변경: 로빙별 독립 handler +conversation_handlers = {} # robing_id별 핸들러 딕셔너리 + +@app.post("/process") +async def process_email_conversation(request: Request): + data = await request.json() + robing_id = data.get("robing_id", "default") + llm_callback = data.get("llm_callback") + + # 로빙별 핸들러 가져오기 또는 생성 + if robing_id not in conversation_handlers: + conversation_handlers[robing_id] = ConversationHandler( + llm_callback_url=llm_callback + ) + else: + # callback URL 업데이트 + handler = conversation_handlers[robing_id] + if llm_callback and handler.llm_callback_url != llm_callback: + handler.llm_callback_url = llm_callback + + handler = conversation_handlers[robing_id] + result = await handler.process_message(...) +``` + +#### B. ConversationHandler Callback 지원 (`/home/heejae/skill-email/handlers/conversation_handler.py`) + +**1. 초기화 변경** +```python +# 이전 +def __init__(self, llm_service_url: str = "http://localhost:8003"): + self.llm_service_url = llm_service_url + +# 변경 +def __init__(self, llm_callback_url: Optional[str] = None): + self.llm_callback_url = llm_callback_url # 로빙의 LLM callback URL +``` + +**2. LLM 추출 로직** +```python +async def _extract_email_info(self, message: str, robing_id: str, + llm_hints: Optional[Dict] = None) -> Dict: + """이메일 정보 추출 - callback URL 또는 자체 파싱 사용""" + + # 1. Callback URL이 있으면 로빙의 LLM 사용 + if self.llm_callback_url: + try: + response = await self.http_client.post( + self.llm_callback_url, + json={ + "message": message, + "task": "extract_email_info", + "robing_id": robing_id + } + ) + + if response.status_code == 200: + result = response.json() + extracted = result.get("result", {}) + + # 자체 파싱으로 보완 (이메일 주소는 정규식으로 확실하게) + parsed = self._parse_email_info(message, llm_hints) + + # 병합 (파싱된 이메일 주소 우선) + return { + "to": parsed.get("to") or extracted.get("to"), + "subject": extracted.get("subject") or parsed.get("subject"), + "body": extracted.get("body") or parsed.get("body") + } + except Exception as e: + logger.error(f"LLM callback error: {e}") + + # 2. Callback이 없거나 실패하면 자체 파싱 + return self._parse_email_info(message, llm_hints) +``` + +**3. 이메일 주소 필수 요구** +```python +def _parse_email_info(self, message: str, llm_hints: Optional[Dict] = None) -> Dict: + """이메일 정보 파싱 - 이메일 주소는 정규식으로만 추출""" + import re + + info = {"to": None, "subject": None, "body": None} + + # 1. 정규식으로 이메일 주소 찾기 (필수) - 더 유연한 패턴 + email_pattern = r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}' + emails = re.findall(email_pattern, message) + if emails: + info["to"] = emails[0] + # 이름만 있으면 None 유지 - 명시적 이메일 주소 요구 + + # 2. 제목/본문은 키워드 또는 LLM 힌트 사용 + # ... + + return info +``` + +### 3.2 rb8001 변경사항 + +#### A. LLM Callback 엔드포인트 추가 (`/home/heejae/rb8001/main.py`) +```python +@app.post("/api/llm/extract") +async def llm_extract_callback(request: Request): + """skill-email이 사용하는 LLM callback 엔드포인트""" + try: + data = await request.json() + message = data.get("message", "") + task = data.get("task", "") + robing_id = data.get("robing_id", "") + + if task == "extract_email_info": + # rb8001의 LLM으로 이메일 정보 추출 + prompt = f""" +사용자의 이메일 요청에서 정보를 추출해주세요. + +메시지: "{message}" + +다음 정보를 찾아주세요: +1. 수신자 이메일 주소 (이메일 형식만, 이름은 제외) +2. 이메일 제목 (추측 가능하면) +3. 이메일 본문 (추측 가능하면) + +JSON 형식으로 응답: +{{ + "to": "이메일 주소 또는 null", + "subject": "제목 또는 null", + "body": "본문 또는 null" +}} +""" + # LLM 서비스 호출 + result = await router._call_internal_llm( + message=prompt, + user_id="system", + channel="system" + ) + + # 응답 파싱 및 JSON 추출 + content = result.get("content", "{}") + extracted = {"to": None, "subject": None, "body": None} + + try: + # JSON 블록 파싱 + if "```json" in content: + json_start = content.find("```json") + 7 + json_end = content.find("```", json_start) + json_str = content[json_start:json_end].strip() + else: + json_str = content + + extracted = json.loads(json_str) + except: + # 휴리스틱 fallback + if "회의" in message: + extracted["subject"] = "회의 일정 안내" + elif "감사" in message: + extracted["subject"] = "감사 인사" + + return {"result": extracted} + + else: + # 다른 task는 일반 처리 + result = await router._call_internal_llm( + message=message, + user_id="system", + channel="system" + ) + return {"result": result.get("content", "")} + + except Exception as e: + logger.error(f"LLM callback error: {e}") + return {"error": str(e), "result": {}} +``` + +#### B. skill-email 호출 시 Callback URL 전달 (`/home/heejae/rb8001/app/skills/email_integration.py`) +```python +async def process_email_request(self, message: str, user_id: str, channel: str = None): + # ... + + # skill-email 서비스 호출 + async with httpx.AsyncClient(timeout=30.0) as client: + if email_intent["action"] == "send": + response = await client.post( + f"{self.skill_email_url}/process", + json={ + "message": message, + "user_id": user_id, + "channel": channel or "", + "robing_id": "rb8001", + "llm_hints": llm_hints, + "llm_callback": "http://localhost:8001/api/llm/extract" # ← Callback URL + } + ) +``` + +--- + +## 4. 핵심 원칙 구현 + +### 4.1 책임 분리 +- **rb8001 (및 다른 로빙)**: 고수준 추론, 의도 파악, LLM 제공 +- **skill-email**: 이메일 도메인 전문 처리 (파싱, 검증, 발송) + +### 4.2 이메일 주소 필수 정책 +- 이름만으로는 절대 발송하지 않음 +- 명시적 이메일 주소가 없으면 반드시 요청 +- 예: "종태님한테" → "받는 사람의 이메일 주소를 알려주세요. (예: example@gmail.com)" + +### 4.3 다중 로빙 지원 +- 각 로빙이 독립적인 handler 인스턴스 사용 +- 대화 상태가 섞이지 않음 +- 각자의 LLM callback 사용 가능 + +--- + +## 5. 테스트 결과 + +### 5.1 Callback 엔드포인트 테스트 +```bash +curl -X POST http://localhost:8001/api/llm/extract \ + -H "Content-Type: application/json" \ + -d '{"message": "종태님한테 회의 메일 보내줘", "task": "extract_email_info", "robing_id": "rb8001"}' + +# 응답 +{ + "result": { + "to": null, + "subject": "회의", + "body": "회의 요청드립니다." + } +} +``` + +### 5.2 통합 테스트 +```python +# test_callback_integration.py 실행 결과 + +# 1. 이메일 주소 파싱 ✓ +Message: test@example.com에게 회의 메일 보내줘 +Response: 받는 사람: test@example.com + 이메일 제목을 알려주세요... + +# 2. LLM Callback 사용 ✓ +rb8001 with callback URL → LLM이 제목 추출 +rb8002 without callback → 자체 파싱만 사용 + +# 3. 세션 독립성 ✓ +rb8001 draft to: admin@company.com +rb8002 draft to: test@example.com +(각자 독립적인 대화 상태 유지) +``` + +--- + +## 6. 주요 개선 사항 + +### 이전 문제점 +1. **LLM 중복 호출**: skill-email이 독립적으로 LLM 서비스 호출 +2. **전역 handler 충돌**: 여러 로빙이 같은 handler 공유 +3. **이름-이메일 매핑**: 복잡한 디렉토리 관리 필요 + +### 해결된 내용 +1. **Callback 패턴**: 로빙이 자신의 LLM 제공 +2. **로빙별 독립 handler**: 대화 상태 완전 분리 +3. **명시적 이메일 주소 요구**: 안전하고 명확한 처리 + +--- + +## 7. 디렉토리 구조 + +``` +/home/heejae/ +├── rb8001/ +│ ├── main.py # /api/llm/extract 엔드포인트 추가 +│ ├── app/skills/ +│ │ └── email_integration.py # llm_callback URL 전달 +│ └── test_callback_integration.py # 통합 테스트 +│ +└── skill-email/ + ├── main.py # 로빙별 handler 관리 + └── handlers/ + └── conversation_handler.py # Callback URL 지원 +``` + +--- + +## 8. 데이터 플로우 + +### 8.1 초기 요청 +``` +사용자: "종태님한테 회의 메일 보내줘" + ↓ +rb8001: + - Gmail 장착 확인 + - 의도 파악 + - skill-email 호출 (callback URL 포함) +``` + +### 8.2 skill-email 처리 +``` +skill-email: + - robing_id로 handler 선택/생성 + - callback URL 설정 + - 이메일 정보 추출 필요 + ↓ + rb8001/api/llm/extract 호출 + ↓ + rb8001 LLM: 제목 "회의 일정 안내" 추출 + ↓ + 자체 파싱: 이메일 주소 없음 확인 + ↓ + 응답: "받는 사람의 이메일 주소를 알려주세요" +``` + +### 8.3 연속 대화 +``` +사용자: "goeun2dc@gmail.com" + ↓ +skill-email: + - 기존 handler["rb8001"] 사용 + - draft 업데이트 + - 이메일 주소 파싱 성공 + - 제목은 이미 LLM이 추출함 + ↓ + 응답: "이메일 본문 내용을 알려주세요" +``` + +--- + +## 9. 보안 및 안정성 + +### 9.1 보안 강화 +- 이메일 주소 명시적 확인 (이름만으로 발송 방지) +- 로빙별 독립 세션 (정보 혼재 방지) +- PostgreSQL 기반 토큰 관리 (중앙집중식) + +### 9.2 안정성 개선 +- Callback 실패 시 자체 파싱 fallback +- 타임아웃 설정 (30초) +- 에러 처리 및 로깅 + +--- + +## 10. 성능 최적화 + +1. **Handler 재사용**: 로빙별로 한 번 생성 후 계속 사용 +2. **선택적 LLM 호출**: 필요한 경우에만 callback +3. **병렬 처리 가능**: 여러 로빙이 동시 사용 가능 + +--- + +## 11. 향후 확장 가능성 + +### 11.1 WebSocket 지원 +```python +# 실시간 양방향 통신 +@app.websocket("/ws/{robing_id}") +async def websocket_endpoint(websocket: WebSocket, robing_id: str): + # 지속적인 대화 세션 +``` + +### 11.2 다양한 Callback Task +```python +# 이메일 외 다른 정보 추출 +if task == "extract_calendar_info": + # 일정 정보 추출 +elif task == "extract_contact_info": + # 연락처 정보 추출 +``` + +### 11.3 로빙별 커스터마이징 +```python +# 각 로빙의 특성에 맞는 LLM 프롬프트 +robing_prompts = { + "rb8001": "정중하고 격식있게", + "rb8002": "친근하고 캐주얼하게" +} +``` + +--- + +## 12. 배포 및 운영 + +### 12.1 Docker 재빌드 +```bash +# rb8001 +cd /home/heejae/rb8001 +docker compose down +docker compose up -d --build + +# skill-email +cd /home/heejae/skill-email +docker stop skill-email && docker rm skill-email +docker build -t skill-email . +docker run -d --name skill-email -p 8501:8501 --env-file .env skill-email +``` + +### 12.2 헬스체크 +```bash +# rb8001 callback 엔드포인트 +curl http://localhost:8001/api/llm/extract + +# skill-email +curl http://localhost:8501/health +``` + +--- + +## 13. 문제 해결 기록 + +### 13.1 이메일 주소 파싱 실패 +- **문제**: `\b` word boundary가 한글과 충돌 +- **해결**: 정규식 패턴 단순화 +```python +# 이전: r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' +# 변경: r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}' +``` + +### 13.2 action 파라미터 오류 +- **문제**: `_call_internal_llm()` got unexpected keyword argument 'action' +- **해결**: action 파라미터 제거 + +### 13.3 컨테이너 재시작 문제 +- **문제**: 코드 변경이 적용되지 않음 +- **해결**: docker compose down & up --build 사용 + +--- + +## 14. 검증 체크리스트 + +- [x] LLM callback 엔드포인트 동작 +- [x] 로빙별 handler 독립성 +- [x] 이메일 주소 필수 요구 +- [x] Callback URL 전달 +- [x] Fallback 자체 파싱 +- [x] 세션 격리 테스트 +- [x] 다중 로빙 동시 사용 +- [x] 에러 처리 동작 + +--- + +## 15. 결론 + +### 성과 +1. **진정한 Callback 아키텍처** 구현 +2. **다중 로빙 완벽 지원** +3. **이메일 보안 강화** (명시적 주소 요구) +4. **확장 가능한 구조** 확립 + +### 핵심 가치 +- **독립성**: 각 로빙이 독립적으로 동작 +- **유연성**: Callback 있어도/없어도 동작 +- **안전성**: 명확한 이메일 주소 확인 +- **확장성**: 새로운 로빙 추가 용이 + +--- + +**작업 완료: 2025-08-20** +**다음 단계: 실제 Gmail API 발송 테스트 및 프로덕션 배포** \ No newline at end of file