# 대화형 점진적 의도 구축 시스템 (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)

---

**작성자 노트**: 
이 시스템은 "기억하고 성장하는 디지털 동료"라는 로빙의 핵심 비전을 실현하는 중요한 단계입니다. 하지만 현실적인 제약을 고려하여 단계적 접근을 권장합니다.
