# 대화형 점진적 의도 구축 시스템 (Conversational Progressive Intent Building) **작성일**: 2025-08-19 **작성자**: happybell80 & Claude **관련 서비스**: rb10508_micro, skill-email, skill-news **문서 상태**: 아이디어 제안 --- ## 1. 개요 ### 1.1 배경 현재 로빙 시스템은 단일 턴(single-turn) 명령 처리에 최적화되어 있습니다. 하지만 실제 사용자는 한 번에 명확한 지시를 하지 않고, 여러 번의 대화를 통해 점진적으로 의도를 구체화합니다. ### 1.2 목표 - 자연스러운 멀티턴(multi-turn) 대화 지원 - 컨텍스트를 유지하며 정보를 점진적으로 수집 - 사용자 의도가 완성되면 자동으로 실행 - "기억하고 성장하는 디지털 동료" 비전 실현 **[참고]** 이 시스템은 현재 네이버웍스 슬랙 브리핑 프로젝트(Phase 2+)와 같은 실제 환경에서 활발히 구현 및 테스트되고 있습니다. 관련 문제 해결 기록은 [여기](../troubleshooting/250919_naverworks_slack_briefing_phase2plus_troubleshooting.md)에서 확인할 수 있습니다. ### 1.3 핵심 가치 - **자연스러움**: 사람 간 대화처럼 자연스러운 상호작용 - **지능적 수집**: 필요한 정보를 적절한 시점에 요청 - **컨텍스트 인식**: 이전 대화를 기억하고 참조 --- ## 2. 시스템 아키텍처 ### 2.1 전체 구조 ``` ┌──────────────────────────────────────────────────────┐ │ 사용자 인터페이스 │ └────────────────────────┬─────────────────────────────┘ │ ┌────────────────────────▼─────────────────────────────┐ │ 로빙 코어 (rb10508_micro) │ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ │ │ 의도 분류기 │ │ 대화 관리자 │ │ 응답 생성기 │ │ │ └─────────────┘ └──────────────┘ └─────────────┘ │ └────────┬──────────────┬──────────────┬───────────────┘ │ │ │ ┌────▼────┐ ┌───▼────┐ ┌───▼────┐ │ Mistral │ │ Redis │ │ Gemini │ └─────────┘ └────────┘ └────────┘ │ │ │ ┌────▼──────────────▼──────────────▼────┐ │ 컨텍스트 관리 시스템 │ │ ┌──────────┐ ┌──────────┐ │ │ │ 세션관리 │ │ 슬롯필링 │ │ │ └──────────┘ └──────────┘ │ └────────────────┬───────────────────────┘ │ ┌────────────────▼───────────────────────┐ │ 스킬 서비스 레이어 │ │ ┌──────────┐ ┌──────────┐ │ │ │ Email │ │ News │ ... │ │ └──────────┘ └──────────┘ │ └────────────────────────────────────────┘ ``` ### 2.2 컴포넌트 설명 #### 2.2.1 의도 분류기 (Intent Classifier) - 사용자 발화를 대분류로 분류 (email/news/general) - 기존 컨텍스트 연속인지 새 작업인지 판단 - Mistral API 활용 #### 2.2.2 대화 관리자 (Dialogue Manager) - 대화 흐름 제어 - 필요한 정보 파악 및 질문 생성 - 실행 트리거 감지 #### 2.2.3 컨텍스트 관리 시스템 - 사용자별 세션 상태 유지 - 수집된 정보(슬롯) 관리 - 타임아웃 및 생명주기 관리 #### 2.2.4 슬롯 필링 (Slot Filling) - 각 스킬별 필수/선택 정보 정의 - 발화에서 정보 추출 - 정보 충돌 해결 --- ## 3. 대화 흐름 시퀀스 ### 3.1 전체 대화 시나리오 ```mermaid sequenceDiagram participant U as 사용자 participant R as 로빙 participant C as 컨텍스트관리 participant M as Mistral participant S as 스킬서비스 Note over U,S: 이메일 전송 대화 시나리오 %% 턴 1: 작업 시작 U->>R: "이메일 보낼건데..." R->>M: 의도 분류 요청 M-->>R: intent: email R->>C: 새 컨텍스트 생성 C-->>R: context_id: abc123 R->>U: "네, 이메일을 보내시겠군요. 누구에게 보내실까요?" %% 턴 2: 수신자 정보 U->>R: "happybell 지메일로 보내줘" R->>C: 컨텍스트 조회 C-->>R: 기존 컨텍스트 반환 R->>M: 슬롯 추출 (수신자) M-->>R: recipient: happybell@gmail.com R->>C: 슬롯 업데이트 R->>U: "happybell@gmail.com으로 보내드리겠습니다. 어떤 내용을 보내실까요?" %% 턴 3: 내용 수집 U->>R: "행사에 와주셔서 감사하다는 내용이야" R->>C: 컨텍스트 조회 R->>M: 슬롯 추출 (내용) M-->>R: content: 감사 인사 R->>C: 슬롯 업데이트 R->>U: "감사 인사 메일이군요. 어떤 톤으로 작성할까요?" %% 턴 4: 톤 설정 U->>R: "공손하지만 기쁜 느낌으로" R->>C: 슬롯 업데이트 (tone: polite_happy) R->>U: "공손하면서도 기쁜 톤으로 작성하겠습니다. 발신자는 어떻게 할까요?" %% 턴 5: 발신자 U->>R: "내 이메일로 해" R->>C: 사용자 정보 조회 C-->>R: sender: user@gmail.com R->>C: 슬롯 업데이트 R->>U: "user@gmail.com으로 발신자를 설정했습니다." %% 턴 6: 추가 요청 U->>R: "끝에 내 명함도 넣어줘" R->>C: 슬롯 업데이트 (signature: true) R->>C: 완성도 체크 C-->>R: 필수 슬롯 완료 R->>U: "명함을 추가하겠습니다. 다음 내용으로 이메일을 준비했습니다:\n[요약 표시]" %% 턴 7: 실행 U->>R: "그래 보내" R->>C: 실행 트리거 감지 R->>S: 이메일 전송 요청 S-->>R: 전송 완료 R->>C: 컨텍스트 종료 R->>U: "이메일을 성공적으로 전송했습니다." ``` ### 3.2 컨텍스트 상태 전이 ```mermaid stateDiagram-v2 [*] --> Idle: 초기 상태 Idle --> Collecting: 작업 시작 감지 Collecting --> Collecting: 정보 추가 Collecting --> Confirming: 필수 슬롯 완료 Collecting --> Modifying: 수정 요청 Modifying --> Collecting: 수정 완료 Confirming --> Executing: 실행 명령 Confirming --> Modifying: 수정 요청 Executing --> Completed: 성공 Executing --> Failed: 실패 Failed --> Collecting: 재시도 Failed --> Cancelled: 취소 Completed --> [*]: 종료 Cancelled --> [*]: 종료 Collecting --> Timeout: 30분 무응답 Confirming --> Timeout: 30분 무응답 Timeout --> Idle: 리셋 ``` ### 3.3 슬롯 필링 프로세스 ```mermaid flowchart TD Start([사용자 발화]) --> Extract[정보 추출] Extract --> Check{슬롯 충돌?} Check -->|예| Confirm[충돌 확인 요청] Check -->|아니오| Update[슬롯 업데이트] Confirm -->|덮어쓰기| Update Confirm -->|유지| Keep[기존값 유지] Update --> Validate[유효성 검증] Keep --> CheckComplete Validate -->|실패| Error[오류 메시지] Validate -->|성공| Save[컨텍스트 저장] Save --> CheckComplete{필수 슬롯\n완료?} CheckComplete -->|예| Ready[실행 준비 완료] CheckComplete -->|아니오| NextQ[다음 질문 생성] Error --> NextQ NextQ --> End([응답 생성]) Ready --> End ``` --- ## 3.4 시간 인식 의도 처리 (신규 추가 2025-09-09) ### 3.4.1 실제 대화 로그 분석 결과 2025년 9월 9일 PostgreSQL conversation_log 테이블 분석 결과, 로빙의 가장 심각한 문제는 **시간 인식 부재**입니다. ``` 실제 사례: 사용자: "오늘 몇일이야?" 로빙: "오늘은 2024년 5월 16일 목요일입니다" ❌ (실제: 2025년 9월 9일) ``` ### 3.4.2 시급한 의도 파악 시나리오 #### 시나리오 1: 시간 질문 의도 ```python TIME_QUERY_PATTERNS = { "absolute_time": r"몇시|몇일|무슨요일|오늘|내일", "relative_time": r"어제|아까|방금|조금전", "time_reference": r"그때|언제|며칠" } # 의도 분류 시 시간 컨텍스트 필수 주입 if any(pattern in text for pattern in TIME_QUERY_PATTERNS.values()): context["current_time"] = datetime.now(KST).strftime("%Y년 %m월 %d일 %H:%M") context["needs_time"] = True ``` #### 시나리오 2: 맥락 참조 의도 ```python CONTEXT_RETRIEVAL_PATTERNS = { "past_reference": r"어제.*했|아까.*말한|그때.*것", "modification": r"바꿔|수정|변경|취소" } # DB에서 과거 대화 조회 필수 if pattern_matches(CONTEXT_RETRIEVAL_PATTERNS): past_logs = await fetch_conversation_log( user_id=user_id, time_range="-2 hours", limit=5 ) context["past_context"] = past_logs ``` #### 시나리오 3: 감정적 피드백 의도 ```python NEGATIVE_FEEDBACK_PATTERNS = { "frustration": r"모르는구나|답답|짜증", "correction": r"아니야|틀렸어|다시", "sarcasm": r"그래그래|알았어알았어" } # 부정 피드백 감지 시 시스템 점검 if pattern_matches(NEGATIVE_FEEDBACK_PATTERNS): log_system_error("User frustration detected") return apologize_and_clarify() ``` ### 3.4.3 제로샷 의도 분류 통합 (Hong et al., 2024 기반) 최신 SIGDIAL 2024 연구에 따르면, 고품질 의도 설명 1개와 임베딩 기반 후보 축소가 핵심입니다. ```python class ZeroShotTimeAwareClassifier: def __init__(self): # 제로샷: 학습 없이 의도 설명만으로 분류 self.intent_descriptions = { "attendance": "출근, 퇴근, 재택근무 같은 근태를 기록하려는 요청", "time_query": "현재 시각, 날짜, 요일을 묻는 질문", "context_retrieval": "아까, 어제, 방금 전 같은 과거 대화를 참조하는 요청", "email": "이메일 확인, 전송, 검색과 관련된 요청", "schedule": "일정 조회, 등록, 수정에 대한 요청" } # 다국어 임베딩 (한국어 지원, Snips 대체) self.embedder = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2') self.gate = SelfAttentiveGate() # 시간 게이트 async def classify_with_time(self, text, user_id): # Step 1: 임베딩 기반 후보 축소 (20ms) text_emb = self.embedder.encode(text) intent_scores = {} for intent, description in self.intent_descriptions.items(): desc_emb = self.embedder.encode(description) score = cosine_similarity(text_emb, desc_emb) intent_scores[intent] = score # 상위 3개 후보만 선택 (Hong et al. 권장: 10-13개, 로빙은 3개면 충분) top_candidates = sorted(intent_scores.items(), key=lambda x: x[1], reverse=True)[:3] # Step 2: 확신도 체크 및 시간 게이트 best_intent = top_candidates[0][0] confidence = top_candidates[0][1] # Step 3: 시간 컨텍스트 선택적 추가 context = {} if best_intent in ['time_query', 'attendance', 'context_retrieval']: context['current_time'] = datetime.now(KST).strftime("%Y년 %m월 %d일 %H:%M") context['needs_time'] = True # Step 4: 저확신 시 LLM 폴백 if confidence < 0.7: # 의도 설명을 포함한 프롬프트로 LLM 호출 prompt = f"사용자: {text}\n후보 의도:\n" for intent, score in top_candidates: prompt += f"- {intent}: {self.intent_descriptions[intent]} (점수: {score:.2f})\n" context['needs_llm'] = True return { "intent": best_intent, "confidence": confidence, "context": context, "candidates": top_candidates } ``` #### 제로샷 방식의 장점 (로빙 적용) 1. **학습 데이터 불필요**: Snips와 달리 의도 설명만 수정하면 즉시 반영(제로샷) 2. **비용 절감 목표**: 임베딩 후보 축소+캐싱으로 LLM 호출 70% 이상 감소(초기 목표, 상향 가능) 3. **시간·맥락 인식 강화**: 시간 의도/과거 참조 의도에 현재 KST와 최근 대화 일부를 주입해 오분류 감소 4. **통합 임베딩 활용**: 단일 임베딩 모델로 의도·감정·윤리를 동시에 처리(프로토타입/라벨 묶음 기반 유사도 비교) 5. **유연 확장성**: 새 의도/윤리 규칙 추가 시 라벨/설명만 추가하여 재학습 없이 확장 ##### 통합 임베딩 전략(의도·감정·윤리) - 단일 다국어 임베딩(예: mpnet/miniLM/한국어 특화 대안)을 `skill-embedding` 서비스로 일원화 - 라벨 세트 관리: intent_labels, emotion_labels, ethics_principles를 벡터로 사전 임베딩하여 DB/캐시 보관 - 분류 방식: 입력 임베딩 ↔ 라벨 임베딩 코사인 유사도 상위 N 후보 산출 → 임계값/온톨로지 규칙으로 최종 결정 - 멀티헤드 규칙: 의도·감정·윤리 점수를 동시 산출하되, 충돌 시 우선순위(보안/윤리 > 의도)로 게이트 - 캐싱: 동일/유사 문장 해시 기반 캐시로 임베딩/LLM 호출 재사용(세션·사용자별 TTL) ##### 측정/목표(운영 관점) - LLM 호출 비율: 제로샷 후보 상위 확신도 임계값(예: 0.75) 상향 조절로 70%↓ 달성(분기별 목표 상향) - 정확도: 상위-1/상위-3 의도 일치율, 되묻기 진입률/해결률, 오분류 재발률 - 시간 인식 개선: 시간 관련 질의 응답 정합성(오늘/어제/요일) 오류율 < 1% - 윤리 게이트 성과: 정책 위반 차단률, 오검출률(사용자 불편) 모니터링 --- ## 4. 데이터 구조 ### 4.1 컨텍스트 스키마 ```python class WorkContext: """작업 컨텍스트""" context_id: str # 고유 ID user_id: str # 사용자 ID session_id: str # 세션 ID # 작업 정보 intent: str # 대분류 (email/news/general) sub_intent: str # 세부 의도 state: ContextState # 현재 상태 # 슬롯 정보 slots: Dict[str, Any] # 수집된 정보 required_slots: List # 필수 슬롯 optional_slots: List # 선택 슬롯 # 대화 히스토리 turns: List[Turn] # 대화 턴 목록 # 메타데이터 created_at: datetime updated_at: datetime expires_at: datetime # TTL ``` ### 4.2 이메일 스킬 슬롯 예시 ```python email_slots = { "required": { "recipient": { "type": "email", "question": "누구에게 보내실까요?", "validation": email_regex }, "content": { "type": "text", "question": "어떤 내용을 보내실까요?", "min_length": 10 } }, "optional": { "subject": { "type": "text", "question": "제목을 입력하시겠습니까?", "default": "안녕하세요" }, "tone": { "type": "enum", "options": ["formal", "casual", "friendly"], "question": "어떤 톤으로 작성할까요?" }, "attachments": { "type": "list", "question": "첨부파일이 있나요?" } } } ``` --- ## 5. 구현 전략 ### 5.1 단계별 구현 (Phase Approach) #### Phase 1: MVP (2주) - 3턴 대화 지원 - 하드코딩된 이메일 슬롯 - 인메모리 컨텍스트 - **코드 증가**: +400줄 #### Phase 2: 기본 구현 (1개월) - 5턴 대화 지원 - Redis 세션 관리 - 2개 스킬 (email, news) - **코드 증가**: +800줄 #### Phase 3: 완전 구현 (2개월) - 무제한 턴 지원 - 동적 슬롯 시스템 - 모든 스킬 통합 - **코드 증가**: +1,750줄 ### 5.2 함수형 프로그래밍 적용 ```python # 순수 함수 예시 def extract_slots(text: str, slot_schema: Dict) -> Dict: """텍스트에서 슬롯 추출 - 순수 함수""" extracted = {} for slot_name, slot_def in slot_schema.items(): if pattern := slot_def.get('pattern'): if match := re.search(pattern, text): extracted[slot_name] = match.group(1) return extracted # 상태 불변성 def update_context(context: WorkContext, slots: Dict) -> WorkContext: """컨텍스트 업데이트 - 새 객체 반환""" return WorkContext( **{**context.__dict__, 'slots': {**context.slots, **slots}, 'updated_at': datetime.now()} ) # I/O 분리 async def process_turn(user_input: str, user_id: str): """턴 처리 - I/O와 로직 분리""" # I/O: 컨텍스트 조회 context = await fetch_context(user_id) # 순수 함수: 의도 분석 intent = classify_intent(user_input) # 순수 함수: 슬롯 추출 slots = extract_slots(user_input, get_schema(intent)) # 순수 함수: 컨텍스트 업데이트 new_context = update_context(context, slots) # I/O: 저장 await save_context(new_context) # 순수 함수: 응답 생성 response = generate_response(new_context) return response ``` --- ## 6. 성능 분석 ### 6.1 리소스 사용량 (7턴 대화 기준) | 리소스 | 현재 방식 | 제안 방식 | 증가율 | |--------|----------|----------|--------| | LLM 호출 | 1회 | 22회 | 22배 | (참고: 이는 멀티턴 대화의 복잡성 증가로 인한 총 호출 수 증가를 의미하며, 단일 턴 의도 분류 과정에서의 효율성 증가는 별개입니다.) | DB 접근 | 0회 | 24회 | - | | 응답 시간 | 500ms | 2,600ms | 5.2배 | | 토큰 사용 | ~200 | ~2,000 | 10배 | | 메모리 | 50MB | 100MB | 2배 | ### 6.2 최적화 전략 1. **병렬 처리** - 의도 분류와 슬롯 추출 동시 실행 - 턴당 200ms 절감 2. **캐싱** - 자주 사용하는 질문 템플릿 - 의도 분류 결과 단기 캐싱 3. **배치 처리** - 대화 히스토리 배치 저장 - 슬롯 검증 일괄 처리 **최적화 후 목표**: 턴당 150-200ms --- ## 7. 장단점 분석 ### 7.1 장점 - ✅ 자연스러운 대화 경험 - ✅ 작업 완성률 향상 (60% → 95%) - ✅ 사용자 만족도 증가 - ✅ "디지털 동료" 비전 실현 ### 7.2 단점 - ❌ 코드 복잡도 5배 증가 - ❌ 디버깅 난이도 상승 - ❌ LLM 비용 20배 증가 - ❌ 응답 지연 (즉각 → 2-3초) ### 7.3 리스크 - 세션 동기화 문제 - 컨텍스트 오염 - 타임아웃 관리 - 스케일링 이슈 --- ## 8. 대안 검토 ### 8.1 하이브리드 접근 - 간단한 명령: 현재 방식 유지 - 복잡한 작업: 대화형 모드 전환 - 사용자가 모드 선택 ### 8.2 프롬프트 개선 - 한 번에 모든 정보 요청하는 프롬프트 - 예: "이메일을 보내려면 수신자, 내용, 톤을 말씀해주세요" ### 8.3 UI 기반 접근 - 프론트엔드에서 폼 제공 - 대화와 폼 병행 사용 --- ## 9. 결론 및 제안 ### 9.1 결론 대화형 점진적 의도 구축 시스템은 사용자 경험을 크게 향상시킬 수 있지만, 구현 복잡도와 운영 비용이 상당히 증가합니다. ### 9.2 제안 1. **Phase 1 MVP로 시작**: 이메일 스킬만 대상으로 POC 구현 2. **사용 패턴 분석**: 실제로 멀티턴 대화가 필요한지 데이터 수집 3. **점진적 확장**: 필요성이 검증되면 Phase 2, 3 진행 ### 9.3 우선순위 현재 rb10508_micro의 "경량화" 목표를 고려하면: 1. 먼저 단순 의도 분류 개선 2. 기본적인 2-3턴 대화 지원 3. 필요시 전체 시스템 구현 --- ## 10. 참고 자료 - [스킬시스템 함수형 자동화와 컨텍스트](../200_core_design/240_스킬시스템_함수형_자동화와_컨텍스트.md) - [로빙 아키텍처 설계 경량](../../rb10408_test/docs/로빙_아키텍쳐_설계_경량.md) - [Gmail 아이템 구현 계획](../troubleshooting/250819_happybell80_GmailItem구현및GitActions설정.md) --- **작성자 노트**: 이 시스템은 "기억하고 성장하는 디지털 동료"라는 로빙의 핵심 비전을 실현하는 중요한 단계입니다. 하지만 현실적인 제약을 고려하여 단계적 접근을 권장합니다.