사용자 입력 → Flask API → ChatbotService → RAG 검색 (ChromaDB) → OpenAI API → 응답 생성
- ResponseGenerator: 11단계 응답 생성 파이프라인을 관리합니다.
- 초기 메시지 처리
- 중단 요청 처리
- 주제 이탈 감지
- 감정 분석 수행
- 상태 전환 관리
- LLM 호출
- 리포트 생성
- 이미지 선택
- ChatbotService: 대화 상태 및 플로우를 관리합니다.
- 상태 머신 (DSM):
INITIAL_SETUP→RECALL_UNRESOLVED→RECALL_ATTACHMENT→ ... - 고정 질문 시스템: 각 상태별 사전 정의된 질문
- 턴 수 관리 및 안전장치
- 상태 머신 (DSM):
- RAGService: 벡터 검색 및 임베딩을 담당합니다.
- OpenAI
text-embedding-3-large모델 사용 - ChromaDB
PersistentClient로 영속성 보장 - 유사 사례 검색 (
analyzed_cases.jsonl)
- OpenAI
- EmotionAnalyzer: 미련도를 분석합니다.
- 키워드 기반 초기 분석
- RAG 기반 정규화 (LLM-as-a-Grader)
- 5가지 지표: 애착도, 후회도, 미해결감, 비교 기준, 회피/접근
- ReportGenerator: 개인화된 리포트를 생성합니다.
- LLM 기반 리포트 생성
- 유사 사례를 참고하여 공감 표현
- Flask 3.0: RESTful API 서버
- OpenAI API (gpt-4o-mini): 대화 생성 엔진
- ChromaDB: 벡터 데이터베이스 (임베딩 저장/검색)
- Vanilla JavaScript: 프레임워크 없는 순수 JS
- HTML5/CSS3: 반응형 UI
- Docker: 컨테이너화
- Render.com: 클라우드 배포
- Python 네이티브 지원으로 Flask와 통합이 쉬웠습니다.
- 별도 서버 설치 없이 임베디드 모드로 사용 가능하여 설정이 간편했습니다.
- 벡터 유사도 검색이 빠르고 정확했습니다.
- 문제 인식: LLM은 학습 데이터에 없는 최신 정보나 특정 도메인 지식에 약합니다.
- 해결 방법: ChromaDB에 관련 지식을 저장하고, 관련 정보를 검색하여 프롬프트에 포함시켰습니다.
- 효과: 환각(Hallucination)을 줄이고 정확한 답변 생성이 가능했습니다.
- 문제 인식: 자유로운 대화만으로는 체계적인 인터뷰가 어렵고, 사용자가 주제를 이탈하거나 대화를 중단할 수 있습니다.
- 해결 방법: 상태별로 고정 질문을 정의하고, 턴 수, 점수 임계값, 질문 소진 등 명확한 상태 전환 조건을 설정했습니다. 주제 이탈 감지 및 자동 리다이렉트 기능도 추가했습니다.
- 효과: 체계적인 데이터 수집과 자연스러운 대화 흐름 유지를 통해 사용자 경험을 개선했습니다.
- 문제 인식: 키워드 기반 분석만으로는 사용자의 복잡한 맥락을 파악하기에 부족했습니다.
- 해결 방법: RAG로 검색한 유사 사례를 LLM이 참고하여 점수를 재평가하도록 했습니다.
- 효과: 더 정확하고 일관된 감정 분석 결과를 도출할 수 있었습니다.
- 현상: 상태 전환 시 질문이 중복되거나 대화 흐름이 끊기는 문제가 발생했습니다.
- 원인: 상태 전환 조건이 턴 수, 질문 소진, 점수 임계값 등 여러 개로 나뉘고, 고정 질문과 꼬리 질문 관리 로직이 복잡하게 얽혔습니다.
- 증상: 같은 질문이 반복되거나 상태가 예상치 못하게 전환되었습니다.
- 현상: 사용자 답변과 무관한 사례가 검색되었습니다.
- 원인: 임베딩 모델이 한국어의 미묘한 유사도를 항상 정확히 판단하지 못했고, 유사도 임계값이 너무 낮게 설정되어 있었습니다.
- 증상: "미련이 없어"라는 답변에 "강한 미련" 사례가 검색되는 등 정반대의 결과가 나왔습니다.
- 현상: LLM이 'PD 친구' 캐릭터에서 벗어나 '분석가'처럼 딱딱하게 말하는 경우가 발생했습니다.
- 원인: 시스템 프롬프트가 충분히 강력하지 않았고, "미련도 계산법이 뭐야?" 같은 Prompt Injection 공격에 취약했습니다.
- 증상: 시스템 정보를 노출하거나 캐릭터의 말투를 잃어버렸습니다.
복잡한 상태 전환 로직을 체계화하기 위해 챗봇이 대화의 맥락을 기억하도록 보완했습니다. state_turns(현재 상태 턴)와 turn_count(전체 턴)를 분리하여 관리하여 질문이 중복되는 현상을 방지했습니다. 또한, max_state_turns 도달 시 자동 상태 전환, max_total_turns 도달 시 리포트 제안, low_regret_threshold 기반 조기 종료 등 명확한 안전장치를 추가하여 대화 흐름을 안정적으로 제어했습니다.
LLM의 역할 이탈을 방지하기 위해, 시스템 프롬프트 최상단에 CRITICAL_RULE을 추가했습니다. 이 규칙은 '환승연애 PD 친구' 역할을 절대 벗어나지 않도록 강제하며, 역할 변경이나 시스템 정보 질문 같은 Prompt Injection 공격이 들어올 시 PD 페르소나를 유지하며 친근하게 거부하도록 지시합니다.
- Before: 평균 5초 소요 (매 턴 RAG 검색 + LLM 호출)
- After: 평균 2초로 단축 (60% 개선)
- 방법:
- 대화 중에는 RAG 없이 키워드 기반 분석만 수행 (
use_rag=False) - 최종 리포트 생성 시에만 누적된 대화록 전체로 RAG 수행 (
use_rag=True)
- 대화 중에는 RAG 없이 키워드 기반 분석만 수행 (
- Before: Docker 컨테이너 메모리 800MB 사용
- After: 400MB로 절반 감소
- 방법: 불필요한 라이브러리를 제거하고,
Dockerfile에서 경량 베이스 이미지(python:3.11-slim)를 사용했습니다.
- 현황: 핵심 로직에 대한 단위 테스트가 없습니다.
- 문제: 리팩토링 시 기존 기능의 동작을 보장하기 어렵고, 특히 상태 머신 로직 변경 시 사이드 이펙트를 파악하기 어려웠습니다.
- 교훈: TDD(Test-Driven Development) 방식의 도입 필요성을 느꼈습니다.
- 현황: 시스템 프롬프트를 여러 번 수정했지만, A/B 테스트 없이 감에 의존해 진행했습니다.
- 문제: 어떤 프롬프트가 가장 효과적인지 정량적으로 측정하지 못했습니다.
- 향후 계획: 사용자 피드백 수집 시스템을 도입하고 프롬프트 버전별 성능을 비교하는 프로세스를 구축하고 싶습니다.
- RAG 이해도 향상: 이론으로만 알던 RAG를 실제 구현하며 내부 동작 원리를 이해했고, ChromaDB의
distance와similarity개념 차이를 학습했습니다. - LLM-as-a-Grader: RAG와 LLM을 결합해 점수를 정규화하는 패턴을 적용하며 LLM의 활용성을 체감했습니다.
- 프롬프트 엔지니어링: 시스템 프롬프트 최적화와 Prompt Injection 방어 기법을 통해 답변 품질과 안정성을 높이는 경험을 했습니다.
- 상태 머신 설계: 복잡한 대화 흐름을 상태 머신으로 체계화하고, 턴 수 및 임계값 기반의 안전장치를 추가하는 경험을 했습니다.
- Git 협업: PR과 코드 리뷰를 통해 코드 품질을 향상시켰습니다.
- 역할 분담: 프로듀서(기획)와 엔지니어(개발) 간의 명확한 업무 분담으로 효율성을 높일 수 있었습니다.
- 시간 관리: 초반에 프롬프트 흐름과 상태 머신을 더 꼼꼼히 설계했다면 개발 후반부의 리팩토링 시간을 단축할 수 있었을 것입니다.
- 문서화: 개발 중 문서화를 소홀히 하여 이미 정했던 정책을 잊는 경우가 있었습니다. 개발과 문서화를 동시에 진행하는 습관이 필요합니다.
- 다음 프로젝트: 기획 단계에서 Flowchart를 더 명확히 확립한 후 개발을 시작하고, 1주 단위 스프린트와 같은 애자일 방식을 도입해보고 싶습니다.
- 배포 플랫폼: Render.com (무료 플랜)
- 배포 URL: https://sogang-chatbot-lovex.onrender.com