- 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/*)
21 KiB
21 KiB
대화형 점진적 의도 구축 시스템 (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+)와 같은 실제 환경에서 활발히 구현 및 테스트되고 있습니다. 관련 문제 해결 기록은 여기에서 확인할 수 있습니다.
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 전체 대화 시나리오
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 컨텍스트 상태 전이
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 슬롯 필링 프로세스
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: 시간 질문 의도
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: 맥락 참조 의도
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: 감정적 피드백 의도
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개와 임베딩 기반 후보 축소가 핵심입니다.
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
}
제로샷 방식의 장점 (로빙 적용)
- 학습 데이터 불필요: Snips와 달리 의도 설명만 수정하면 즉시 반영(제로샷)
- 비용 절감 목표: 임베딩 후보 축소+캐싱으로 LLM 호출 70% 이상 감소(초기 목표, 상향 가능)
- 시간·맥락 인식 강화: 시간 의도/과거 참조 의도에 현재 KST와 최근 대화 일부를 주입해 오분류 감소
- 통합 임베딩 활용: 단일 임베딩 모델로 의도·감정·윤리를 동시에 처리(프로토타입/라벨 묶음 기반 유사도 비교)
- 유연 확장성: 새 의도/윤리 규칙 추가 시 라벨/설명만 추가하여 재학습 없이 확장
통합 임베딩 전략(의도·감정·윤리)
- 단일 다국어 임베딩(예: mpnet/miniLM/한국어 특화 대안)을
skill-embedding서비스로 일원화 - 라벨 세트 관리: intent_labels, emotion_labels, ethics_principles를 벡터로 사전 임베딩하여 DB/캐시 보관
- 분류 방식: 입력 임베딩 ↔ 라벨 임베딩 코사인 유사도 상위 N 후보 산출 → 임계값/온톨로지 규칙으로 최종 결정
- 멀티헤드 규칙: 의도·감정·윤리 점수를 동시 산출하되, 충돌 시 우선순위(보안/윤리 > 의도)로 게이트
- 캐싱: 동일/유사 문장 해시 기반 캐시로 임베딩/LLM 호출 재사용(세션·사용자별 TTL)
측정/목표(운영 관점)
- LLM 호출 비율: 제로샷 후보 상위 확신도 임계값(예: 0.75) 상향 조절로 70%↓ 달성(분기별 목표 상향)
- 정확도: 상위-1/상위-3 의도 일치율, 되묻기 진입률/해결률, 오분류 재발률
- 시간 인식 개선: 시간 관련 질의 응답 정합성(오늘/어제/요일) 오류율 < 1%
- 윤리 게이트 성과: 정책 위반 차단률, 오검출률(사용자 불편) 모니터링
4. 데이터 구조
4.1 컨텍스트 스키마
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 이메일 스킬 슬롯 예시
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 함수형 프로그래밍 적용
# 순수 함수 예시
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 최적화 전략
-
병렬 처리
- 의도 분류와 슬롯 추출 동시 실행
- 턴당 200ms 절감
-
캐싱
- 자주 사용하는 질문 템플릿
- 의도 분류 결과 단기 캐싱
-
배치 처리
- 대화 히스토리 배치 저장
- 슬롯 검증 일괄 처리
최적화 후 목표: 턴당 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 제안
- Phase 1 MVP로 시작: 이메일 스킬만 대상으로 POC 구현
- 사용 패턴 분석: 실제로 멀티턴 대화가 필요한지 데이터 수집
- 점진적 확장: 필요성이 검증되면 Phase 2, 3 진행
9.3 우선순위
현재 rb10508_micro의 "경량화" 목표를 고려하면:
- 먼저 단순 의도 분류 개선
- 기본적인 2-3턴 대화 지원
- 필요시 전체 시스템 구현
10. 참고 자료
작성자 노트: 이 시스템은 "기억하고 성장하는 디지털 동료"라는 로빙의 핵심 비전을 실현하는 중요한 단계입니다. 하지만 현실적인 제약을 고려하여 단계적 접근을 권장합니다.